Merge "Revert "Cleanup dirty daisy build workspace directory""
authorSerena Feng <feng.xiaowei@zte.com.cn>
Fri, 14 Apr 2017 07:15:15 +0000 (07:15 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Fri, 14 Apr 2017 07:15:15 +0000 (07:15 +0000)
615 files changed:
.gitignore
INFO
docs/jenkins-job-builder/index.rst
docs/jenkins-job-builder/opnfv-jjb-usage.rst
jjb-sandbox/releng/releng-sandbox-jobs.yml
jjb/3rd_party_ci/create-apex-vms.sh [new file with mode: 0755]
jjb/3rd_party_ci/download-netvirt-artifact.sh [new file with mode: 0755]
jjb/3rd_party_ci/install-netvirt.sh [new file with mode: 0755]
jjb/3rd_party_ci/odl-netvirt.yml [new file with mode: 0644]
jjb/3rd_party_ci/postprocess-netvirt.sh [new file with mode: 0755]
jjb/apex/apex-build.sh
jjb/apex/apex-deploy.sh
jjb/apex/apex-snapshot-create.sh [new file with mode: 0644]
jjb/apex/apex-snapshot-deploy.sh [new file with mode: 0644]
jjb/apex/apex-unit-test.sh
jjb/apex/apex-upload-artifact.sh
jjb/apex/apex.yml
jjb/armband/armband-ci-jobs.yml
jjb/armband/armband-deploy.sh
jjb/armband/armband-download-artifact.sh
jjb/armband/armband-project-jobs.yml
jjb/armband/armband-verify-jobs.yml
jjb/armband/build.sh
jjb/armband/upload-artifacts.sh
jjb/availability/availability.yml
jjb/barometer/barometer.yml [moved from jjb/fastpathmetrics/fastpathmetrics.yml with 79% similarity]
jjb/bottlenecks/bottlenecks-ci-jobs.yml
jjb/bottlenecks/bottlenecks-cleanup.sh [new file with mode: 0644]
jjb/bottlenecks/bottlenecks-project-jobs.yml
jjb/bottlenecks/bottlenecks-run-suite.sh [new file with mode: 0644]
jjb/compass4nfv/compass-ci-jobs.yml
jjb/compass4nfv/compass-deploy.sh
jjb/compass4nfv/compass-dovetail-jobs.yml [new file with mode: 0644]
jjb/compass4nfv/compass-project-jobs.yml
jjb/compass4nfv/compass-verify-jobs.yml
jjb/conductor/conductor.yml
jjb/copper/copper.yml
jjb/cperf/cperf-ci-jobs.yml
jjb/daisy4nfv/daisy-daily-jobs.yml [new file with mode: 0644]
jjb/daisy4nfv/daisy-deploy.sh [new file with mode: 0755]
jjb/daisy4nfv/daisy-project-jobs.yml [new file with mode: 0644]
jjb/daisy4nfv/daisy4nfv-build.sh
jjb/daisy4nfv/daisy4nfv-download-artifact.sh [new file with mode: 0755]
jjb/daisy4nfv/daisy4nfv-merge-jobs.yml [new file with mode: 0644]
jjb/daisy4nfv/daisy4nfv-upload-artifact.sh [new file with mode: 0755]
jjb/daisy4nfv/daisy4nfv-verify-jobs.yml
jjb/daisy4nfv/daisy4nfv-workspace-cleanup.sh [new file with mode: 0755]
jjb/doctor/doctor.yml
jjb/domino/domino.yml
jjb/dovetail/dovetail-artifacts-upload.sh [new file with mode: 0755]
jjb/dovetail/dovetail-artifacts-upload.yml [new file with mode: 0644]
jjb/dovetail/dovetail-ci-jobs.yml
jjb/dovetail/dovetail-cleanup.sh
jjb/dovetail/dovetail-project-jobs.yml
jjb/dovetail/dovetail-run.sh
jjb/dovetail/dovetail-weekly-jobs.yml [new file with mode: 0644]
jjb/dpacc/dpacc.yml
jjb/escalator/escalator-basic.sh [moved from jjb/daisy4nfv/daisy4nfv-virtual-deploy.sh with 75% similarity]
jjb/escalator/escalator-build.sh [new file with mode: 0755]
jjb/escalator/escalator-upload-artifact.sh [new file with mode: 0755]
jjb/escalator/escalator.yml [moved from jjb/fuel/fuel-verify-jobs-experimental.yml with 54% similarity]
jjb/fuel/fuel-basic-exp.sh [deleted file]
jjb/fuel/fuel-build-exp.sh [deleted file]
jjb/fuel/fuel-build.sh
jjb/fuel/fuel-daily-jobs.yml
jjb/fuel/fuel-deploy-exp.sh [deleted file]
jjb/fuel/fuel-deploy.sh
jjb/fuel/fuel-download-artifact.sh
jjb/fuel/fuel-plugin-build.sh [deleted file]
jjb/fuel/fuel-plugin-test.sh [deleted file]
jjb/fuel/fuel-plugin-verify-jobs.yml [deleted file]
jjb/fuel/fuel-project-jobs.yml
jjb/fuel/fuel-smoke-test-exp.sh [deleted file]
jjb/fuel/fuel-upload-artifact.sh
jjb/fuel/fuel-verify-jobs.yml
jjb/fuel/fuel-weekly-jobs.yml [new file with mode: 0644]
jjb/functest/functest-cleanup.sh
jjb/functest/functest-daily-jobs.yml [moved from jjb/functest/functest-ci-jobs.yml with 84% similarity]
jjb/functest/functest-exit.sh
jjb/functest/functest-loop.sh
jjb/functest/functest-project-jobs.yml
jjb/functest/functest-suite.sh
jjb/functest/functest-weekly-jobs.yml [new file with mode: 0644]
jjb/functest/set-functest-env.sh
jjb/global/installer-params.yml [moved from jjb/opnfv/installer-params.yml with 73% similarity]
jjb/global/releng-defaults.yml [new file with mode: 0644]
jjb/global/releng-macros.yml [moved from jjb/releng-macros.yaml with 69% similarity]
jjb/global/slave-params.yml [moved from jjb/opnfv/slave-params.yml with 76% similarity]
jjb/infra/bifrost-verify.sh [deleted file]
jjb/ipv6/ipv6.yml
jjb/joid/joid-daily-jobs.yml
jjb/joid/joid-deploy.sh
jjb/joid/joid-verify-jobs.yml
jjb/kvmfornfv/kvmfornfv-test.sh
jjb/kvmfornfv/kvmfornfv-upload-artifact.sh
jjb/kvmfornfv/kvmfornfv.yml
jjb/models/models.yml [new file with mode: 0644]
jjb/moon/moon.yml
jjb/multisite/fuel-deploy-for-multisite.sh [new file with mode: 0755]
jjb/multisite/multisite-daily-jobs.yml [new file with mode: 0644]
jjb/multisite/multisite-verify-jobs.yml [new file with mode: 0644]
jjb/multisite/multisite.yml [deleted file]
jjb/netready/netready-gluon-build.sh [new file with mode: 0755]
jjb/netready/netready-upload-gluon-packages.sh [new file with mode: 0755]
jjb/netready/netready.yml
jjb/octopus/octopus.yml
jjb/onosfw/onosfw.yml
jjb/openretriever/openretriever-project.yml [moved from jjb/opnfv/opnfv-lint.yml with 57% similarity]
jjb/opera/opera-daily-jobs.yml [new file with mode: 0644]
jjb/opera/opera-project-jobs.yml [new file with mode: 0644]
jjb/opera/opera-verify-jobs.yml [new file with mode: 0644]
jjb/opnfv/opnfv-docker.sh [deleted file]
jjb/opnfv/opnfv-docker.yml [deleted file]
jjb/opnfv/test-sign.yml [deleted file]
jjb/opnfvdocs/docs-rtd.yaml [new file with mode: 0644]
jjb/opnfvdocs/opnfvdocs.yml
jjb/opnfvdocs/project.cfg
jjb/ovsnfv/ovsnfv.yml
jjb/parser/parser.yml
jjb/pharos/pharos.yml
jjb/prediction/prediction.yml
jjb/promise/promise.yml
jjb/qtip/helpers/cleanup-deploy.sh [moved from jjb/qtip/qtip-cleanup.sh with 60% similarity]
jjb/qtip/helpers/validate-deploy.sh [new file with mode: 0644]
jjb/qtip/helpers/validate-setup.sh [new file with mode: 0644]
jjb/qtip/qtip-ci-jobs.yml [deleted file]
jjb/qtip/qtip-daily-ci.sh [deleted file]
jjb/qtip/qtip-validate-jobs.yml [new file with mode: 0644]
jjb/qtip/qtip-verify-jobs.yml [new file with mode: 0644]
jjb/releng-defaults.yaml [deleted file]
jjb/releng/artifact-cleanup.yml [moved from jjb/opnfv/artifact-cleanup.yml with 74% similarity]
jjb/releng/opnfv-docker-arm.yml [new file with mode: 0644]
jjb/releng/opnfv-docker.sh [new file with mode: 0644]
jjb/releng/opnfv-docker.yml [new file with mode: 0644]
jjb/releng/opnfv-docs.yml [moved from jjb/opnfv/opnfv-docs.yml with 87% similarity]
jjb/releng/opnfv-lint.yml [new file with mode: 0644]
jjb/releng/opnfv-utils.yml [moved from jjb/opnfv/opnfv-utils.yml with 100% similarity]
jjb/releng/releng-ci-jobs.yml
jjb/releng/testapi-automate.yml [new file with mode: 0644]
jjb/releng/testapi-backup-mongodb.sh [new file with mode: 0644]
jjb/releng/testapi-docker-deploy.sh [new file with mode: 0644]
jjb/releng/testapi-docker-update.sh [new file with mode: 0644]
jjb/releng/verify-releng.sh
jjb/securityaudit/opnfv-security-audit.yml [new file with mode: 0644]
jjb/snaps/snaps.yml [moved from jjb/qtip/qtip-project-jobs.yml with 75% similarity]
jjb/storperf/storperf.yml
jjb/test-requirements.txt [new file with mode: 0644]
jjb/ves/ves.yml [new file with mode: 0644]
jjb/vnf_forwarding_graph/vnf_forwarding_graph.yml
jjb/vswitchperf/vswitchperf.yml
jjb/xci/bifrost-cleanup-job.yml [new file with mode: 0644]
jjb/xci/bifrost-periodic-jobs.yml [new file with mode: 0644]
jjb/xci/bifrost-provision.sh [new file with mode: 0755]
jjb/xci/bifrost-verify-jobs.yml [moved from jjb/infra/bifrost-verify-jobs.yml with 64% similarity]
jjb/xci/bifrost-verify.sh [new file with mode: 0755]
jjb/xci/osa-periodic-jobs.yml [new file with mode: 0644]
jjb/xci/xci-daily-jobs.yml [new file with mode: 0644]
jjb/xci/xci-deploy.sh [new file with mode: 0755]
jjb/xci/xci-functest.sh [new file with mode: 0755]
jjb/yardstick/yardstick-ci-jobs.yml
jjb/yardstick/yardstick-cleanup.sh
jjb/yardstick/yardstick-daily.sh
jjb/yardstick/yardstick-project-jobs.yml
modules/README.rst [new file with mode: 0644]
modules/opnfv/__init__.py [moved from utils/installer-adapter/__init__.py with 100% similarity]
modules/opnfv/deployment/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/apex/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/apex/adapter.py [new file with mode: 0644]
modules/opnfv/deployment/compass/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/compass/adapter.py [new file with mode: 0644]
modules/opnfv/deployment/example.py [new file with mode: 0644]
modules/opnfv/deployment/factory.py [new file with mode: 0644]
modules/opnfv/deployment/fuel/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/fuel/adapter.py [new file with mode: 0644]
modules/opnfv/deployment/manager.py [new file with mode: 0644]
modules/opnfv/utils/Connection.py [new file with mode: 0644]
modules/opnfv/utils/Credentials.py [new file with mode: 0644]
modules/opnfv/utils/OPNFVExceptions.py [new file with mode: 0644]
modules/opnfv/utils/__init__.py [new file with mode: 0644]
modules/opnfv/utils/constants.py [new file with mode: 0644]
modules/opnfv/utils/opnfv_logger.py [moved from utils/installer-adapter/RelengLogger.py with 100% similarity]
modules/opnfv/utils/ovs_logger.py [new file with mode: 0644]
modules/opnfv/utils/ssh_utils.py [new file with mode: 0644]
modules/requirements.txt [new file with mode: 0644]
modules/run_unit_tests.sh [new file with mode: 0755]
modules/setup.py [new file with mode: 0644]
modules/test-requirements.txt [new file with mode: 0644]
modules/tests/__init__.py [new file with mode: 0644]
modules/tests/unit/__init__.py [new file with mode: 0644]
modules/tests/unit/utils/__init__.py [new file with mode: 0644]
modules/tests/unit/utils/test_OPNFVExceptions.py [new file with mode: 0644]
prototypes/bifrost/README.md
prototypes/bifrost/playbooks/inventory/group_vars/baremetal [new file with mode: 0644]
prototypes/bifrost/playbooks/opnfv-virtual.yaml [moved from prototypes/bifrost/playbooks/test-bifrost-infracloud.yaml with 94% similarity]
prototypes/bifrost/scripts/bifrost-provision.sh [moved from prototypes/bifrost/scripts/test-bifrost-deployment.sh with 58% similarity]
prototypes/bifrost/scripts/destroy-env.sh
prototypes/openstack-ansible/README.md [new file with mode: 0644]
prototypes/openstack-ansible/file/cinder.yml [new file with mode: 0644]
prototypes/openstack-ansible/file/exports [new file with mode: 0644]
prototypes/openstack-ansible/file/modules [new file with mode: 0644]
prototypes/openstack-ansible/file/openstack_user_config.yml [new file with mode: 0644]
prototypes/openstack-ansible/file/opnfv-setup-openstack.yml [new file with mode: 0644]
prototypes/openstack-ansible/file/user_variables.yml [new file with mode: 0644]
prototypes/openstack-ansible/playbooks/configure-targethosts.yml [new file with mode: 0644]
prototypes/openstack-ansible/playbooks/configure-xcimaster.yml [new file with mode: 0644]
prototypes/openstack-ansible/playbooks/inventory [new file with mode: 0644]
prototypes/openstack-ansible/scripts/osa-deploy.sh [new file with mode: 0755]
prototypes/openstack-ansible/template/bifrost/compute.interface.j2 [new file with mode: 0644]
prototypes/openstack-ansible/template/bifrost/controller.interface.j2 [new file with mode: 0644]
prototypes/openstack-ansible/var/ubuntu.yml [new file with mode: 0644]
prototypes/puppet-infracloud/creds/clouds.yaml
prototypes/puppet-infracloud/deploy_on_baremetal.md
prototypes/puppet-infracloud/hiera/common.yaml
prototypes/puppet-infracloud/hiera/common_baremetal.yaml
prototypes/puppet-infracloud/manifests/site.pp
prototypes/puppet-infracloud/modules/opnfv/manifests/server.pp
prototypes/xci/README.rst [new file with mode: 0644]
prototypes/xci/config/aio-vars [new file with mode: 0755]
prototypes/xci/config/env-vars [new file with mode: 0755]
prototypes/xci/config/ha-vars [new file with mode: 0755]
prototypes/xci/config/mini-vars [new file with mode: 0755]
prototypes/xci/config/noha-vars [new file with mode: 0755]
prototypes/xci/config/pinned-versions [new file with mode: 0755]
prototypes/xci/config/user-vars [new file with mode: 0755]
prototypes/xci/docs/developer-guide.rst [new file with mode: 0644]
prototypes/xci/file/aio/configure-opnfvhost.yml [new file with mode: 0644]
prototypes/xci/file/aio/flavor-vars.yml [new file with mode: 0644]
prototypes/xci/file/aio/inventory [new file with mode: 0644]
prototypes/xci/file/ansible-role-requirements.yml [new file with mode: 0644]
prototypes/xci/file/cinder.yml [new file with mode: 0644]
prototypes/xci/file/exports [new file with mode: 0644]
prototypes/xci/file/ha/configure-targethosts.yml [new file with mode: 0644]
prototypes/xci/file/ha/flavor-vars.yml [new file with mode: 0644]
prototypes/xci/file/ha/inventory [new file with mode: 0644]
prototypes/xci/file/ha/openstack_user_config.yml [new file with mode: 0644]
prototypes/xci/file/ha/user_variables.yml [new file with mode: 0644]
prototypes/xci/file/mini/configure-targethosts.yml [new file with mode: 0644]
prototypes/xci/file/mini/flavor-vars.yml [new file with mode: 0644]
prototypes/xci/file/mini/inventory [new file with mode: 0644]
prototypes/xci/file/mini/openstack_user_config.yml [new file with mode: 0644]
prototypes/xci/file/mini/user_variables.yml [new file with mode: 0644]
prototypes/xci/file/modules [new file with mode: 0644]
prototypes/xci/file/noha/configure-targethosts.yml [new file with mode: 0644]
prototypes/xci/file/noha/flavor-vars.yml [new file with mode: 0644]
prototypes/xci/file/noha/inventory [new file with mode: 0644]
prototypes/xci/file/noha/openstack_user_config.yml [new file with mode: 0644]
prototypes/xci/file/noha/user_variables.yml [new file with mode: 0644]
prototypes/xci/file/setup-openstack.yml [new file with mode: 0644]
prototypes/xci/playbooks/configure-localhost.yml [new file with mode: 0644]
prototypes/xci/playbooks/configure-opnfvhost.yml [new file with mode: 0644]
prototypes/xci/playbooks/inventory [moved from utils/test/testapi/opnfv_testapi/common/constants.py with 65% similarity]
prototypes/xci/playbooks/provision-vm-nodes.yml [new file with mode: 0644]
prototypes/xci/playbooks/roles/clone-repository/tasks/main.yml [new file with mode: 0644]
prototypes/xci/playbooks/roles/configure-network/tasks/main.yml [new file with mode: 0644]
prototypes/xci/playbooks/roles/configure-nfs/tasks/main.yml [new file with mode: 0644]
prototypes/xci/playbooks/roles/remove-folders/tasks/main.yml [new file with mode: 0644]
prototypes/xci/template/compute.interface.j2 [new file with mode: 0644]
prototypes/xci/template/controller.interface.j2 [new file with mode: 0644]
prototypes/xci/template/opnfv.interface.j2 [new file with mode: 0644]
prototypes/xci/var/Debian.yml [new file with mode: 0644]
prototypes/xci/var/RedHat.yml [new file with mode: 0644]
prototypes/xci/var/Suse.yml [new file with mode: 0644]
prototypes/xci/var/opnfv.yml [new file with mode: 0644]
prototypes/xci/xci-deploy.sh [new file with mode: 0755]
setup.py [new file with mode: 0644]
tox.ini [new file with mode: 0644]
utils/calculate_version.sh [deleted file]
utils/fetch_os_creds.sh
utils/installer-adapter/ApexAdapter.py [deleted file]
utils/installer-adapter/CompassAdapter.py [deleted file]
utils/installer-adapter/FuelAdapter.py [deleted file]
utils/installer-adapter/InstallerHandler.py [deleted file]
utils/installer-adapter/JoidAdapter.py [deleted file]
utils/installer-adapter/SSHUtils.py [deleted file]
utils/installer-adapter/example.py [deleted file]
utils/jenkins-jnlp-connect.sh
utils/lab-reconfiguration/foreman.yaml
utils/lab-reconfiguration/fuel.yaml
utils/lab-reconfiguration/reconfigUcsNet.py
utils/opnfv-artifacts.py
utils/push-test-logs.sh
utils/test-sign-artifact.sh [deleted file]
utils/test/dashboard.tar.gz [deleted file]
utils/test/dashboard/dashboard/common/elastic_access.py
utils/test/dashboard/dashboard/conf/testcases.py
utils/test/dashboard/dashboard/elastic2kibana/utility.py
utils/test/dashboard/dashboard/functest/format.py
utils/test/dashboard/dashboard/functest/testcases.yaml
utils/test/dashboard/dashboard/mongo2elastic/main.py
utils/test/dashboard/dashboard/qtip/testcases.yaml
utils/test/dashboard/kibana_cleanup.py
utils/test/declaration/addtestcase.php [deleted file]
utils/test/declaration/index.php [deleted file]
utils/test/declaration/testcases.php [deleted file]
utils/test/reporting/3rd_party/css/font-awesome.min.css [moved from utils/test/reporting/assets/css/font-awesome.min.css with 100% similarity]
utils/test/reporting/3rd_party/css/ie8.css [moved from utils/test/reporting/assets/css/ie8.css with 100% similarity]
utils/test/reporting/3rd_party/css/ie9.css [moved from utils/test/reporting/assets/css/ie9.css with 100% similarity]
utils/test/reporting/3rd_party/css/main.css [moved from utils/test/reporting/assets/css/main.css with 100% similarity]
utils/test/reporting/3rd_party/fonts/FontAwesome.otf [moved from utils/test/reporting/assets/fonts/FontAwesome.otf with 100% similarity]
utils/test/reporting/3rd_party/fonts/fontawesome-webfont.eot [moved from utils/test/reporting/assets/fonts/fontawesome-webfont.eot with 100% similarity]
utils/test/reporting/3rd_party/fonts/fontawesome-webfont.svg [moved from utils/test/reporting/assets/fonts/fontawesome-webfont.svg with 100% similarity]
utils/test/reporting/3rd_party/fonts/fontawesome-webfont.ttf [moved from utils/test/reporting/assets/fonts/fontawesome-webfont.ttf with 100% similarity]
utils/test/reporting/3rd_party/fonts/fontawesome-webfont.woff [moved from utils/test/reporting/assets/fonts/fontawesome-webfont.woff with 100% similarity]
utils/test/reporting/3rd_party/fonts/fontawesome-webfont.woff2 [moved from utils/test/reporting/assets/fonts/fontawesome-webfont.woff2 with 100% similarity]
utils/test/reporting/3rd_party/js/ie/html5shiv.js [moved from utils/test/reporting/assets/js/ie/html5shiv.js with 100% similarity]
utils/test/reporting/3rd_party/js/ie/respond.min.js [moved from utils/test/reporting/assets/js/ie/respond.min.js with 100% similarity]
utils/test/reporting/3rd_party/js/jquery.min.js [moved from utils/test/reporting/assets/js/jquery.min.js with 100% similarity]
utils/test/reporting/3rd_party/js/main.js [moved from utils/test/reporting/assets/js/main.js with 100% similarity]
utils/test/reporting/3rd_party/js/skel.min.js [moved from utils/test/reporting/assets/js/skel.min.js with 100% similarity]
utils/test/reporting/3rd_party/js/util.js [moved from utils/test/reporting/assets/js/util.js with 100% similarity]
utils/test/reporting/3rd_party/sass/base/_page.scss [moved from utils/test/reporting/assets/sass/base/_page.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/base/_typography.scss [moved from utils/test/reporting/assets/sass/base/_typography.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_box.scss [moved from utils/test/reporting/assets/sass/components/_box.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_button.scss [moved from utils/test/reporting/assets/sass/components/_button.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_form.scss [moved from utils/test/reporting/assets/sass/components/_form.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_icon.scss [moved from utils/test/reporting/assets/sass/components/_icon.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_image.scss [moved from utils/test/reporting/assets/sass/components/_image.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_list.scss [moved from utils/test/reporting/assets/sass/components/_list.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_section.scss [moved from utils/test/reporting/assets/sass/components/_section.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_table.scss [moved from utils/test/reporting/assets/sass/components/_table.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/components/_tiles.scss [moved from utils/test/reporting/assets/sass/components/_tiles.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/ie8.scss [moved from utils/test/reporting/assets/sass/ie8.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/ie9.scss [moved from utils/test/reporting/assets/sass/ie9.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/layout/_footer.scss [moved from utils/test/reporting/assets/sass/layout/_footer.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/layout/_header.scss [moved from utils/test/reporting/assets/sass/layout/_header.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/layout/_main.scss [moved from utils/test/reporting/assets/sass/layout/_main.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/layout/_menu.scss [moved from utils/test/reporting/assets/sass/layout/_menu.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/layout/_wrapper.scss [moved from utils/test/reporting/assets/sass/layout/_wrapper.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/libs/_functions.scss [moved from utils/test/reporting/assets/sass/libs/_functions.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/libs/_mixins.scss [moved from utils/test/reporting/assets/sass/libs/_mixins.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/libs/_skel.scss [moved from utils/test/reporting/assets/sass/libs/_skel.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/libs/_vars.scss [moved from utils/test/reporting/assets/sass/libs/_vars.scss with 100% similarity]
utils/test/reporting/3rd_party/sass/main.scss [moved from utils/test/reporting/assets/sass/main.scss with 100% similarity]
utils/test/reporting/api/api/__init__.py [new file with mode: 0644]
utils/test/reporting/api/api/conf.py [new file with mode: 0644]
utils/test/reporting/api/api/handlers/__init__.py [new file with mode: 0644]
utils/test/reporting/api/api/handlers/landing.py [new file with mode: 0644]
utils/test/reporting/api/api/handlers/projects.py [new file with mode: 0644]
utils/test/reporting/api/api/handlers/testcases.py [new file with mode: 0644]
utils/test/reporting/api/api/server.py [new file with mode: 0644]
utils/test/reporting/api/api/urls.py [new file with mode: 0644]
utils/test/reporting/api/requirements.txt [new file with mode: 0644]
utils/test/reporting/api/setup.cfg [new file with mode: 0644]
utils/test/reporting/api/setup.py [new file with mode: 0644]
utils/test/reporting/docker/Dockerfile [new file with mode: 0644]
utils/test/reporting/docker/nginx.conf [new file with mode: 0644]
utils/test/reporting/docker/reporting.sh [new file with mode: 0755]
utils/test/reporting/docker/requirements.pip [new file with mode: 0644]
utils/test/reporting/docker/supervisor.conf [new file with mode: 0644]
utils/test/reporting/functest/reporting-status.py
utils/test/reporting/functest/reporting-tempest.py
utils/test/reporting/functest/reporting-vims.py
utils/test/reporting/functest/reportingConf.py [deleted file]
utils/test/reporting/functest/reportingUtils.py [deleted file]
utils/test/reporting/functest/template/index-status-tmpl.html
utils/test/reporting/functest/template/index-tempest-tmpl.html
utils/test/reporting/functest/template/index-vims-tmpl.html
utils/test/reporting/functest/testCase.py
utils/test/reporting/html/colorado.html [moved from utils/test/reporting/colorado.html with 72% similarity]
utils/test/reporting/html/danube.html [new file with mode: 0644]
utils/test/reporting/html/elements.html [moved from utils/test/reporting/elements.html with 76% similarity]
utils/test/reporting/html/functest-colorado.html [moved from utils/test/reporting/functest-colorado.html with 68% similarity]
utils/test/reporting/html/functest-danube.html [moved from utils/test/reporting/danube.html with 63% similarity]
utils/test/reporting/html/functest-master.html [moved from utils/test/reporting/functest-master.html with 68% similarity]
utils/test/reporting/html/generic.html [moved from utils/test/reporting/generic.html with 83% similarity]
utils/test/reporting/html/index.html [moved from utils/test/reporting/index.html with 69% similarity]
utils/test/reporting/html/master.html [new file with mode: 0644]
utils/test/reporting/images/danube.jpg [deleted file]
utils/test/reporting/images/functest.jpg [deleted file]
utils/test/reporting/images/yardstick.jpg [deleted file]
utils/test/reporting/img/colorado.jpg [moved from utils/test/reporting/images/colorado.jpg with 100% similarity]
utils/test/reporting/img/danube.jpg [new file with mode: 0644]
utils/test/reporting/img/euphrates.jpg [new file with mode: 0644]
utils/test/reporting/img/functest.jpg.old [moved from utils/test/reporting/images/functest.jpg.old with 100% similarity]
utils/test/reporting/img/gauge_0.png [new file with mode: 0644]
utils/test/reporting/img/gauge_100.png [new file with mode: 0644]
utils/test/reporting/img/gauge_16.7.png [new file with mode: 0644]
utils/test/reporting/img/gauge_25.png [new file with mode: 0644]
utils/test/reporting/img/gauge_33.3.png [new file with mode: 0644]
utils/test/reporting/img/gauge_41.7.png [new file with mode: 0644]
utils/test/reporting/img/gauge_50.png [new file with mode: 0644]
utils/test/reporting/img/gauge_58.3.png [new file with mode: 0644]
utils/test/reporting/img/gauge_66.7.png [new file with mode: 0644]
utils/test/reporting/img/gauge_75.png [new file with mode: 0644]
utils/test/reporting/img/gauge_8.3.png [new file with mode: 0644]
utils/test/reporting/img/gauge_83.3.png [new file with mode: 0644]
utils/test/reporting/img/gauge_91.7.png [new file with mode: 0644]
utils/test/reporting/img/icon-nok.png [new file with mode: 0644]
utils/test/reporting/img/icon-ok.png [new file with mode: 0644]
utils/test/reporting/img/logo.svg [moved from utils/test/reporting/images/logo.svg with 100% similarity]
utils/test/reporting/img/pic01.jpg [moved from utils/test/reporting/images/pic01.jpg with 100% similarity]
utils/test/reporting/img/pic02.jpg [moved from utils/test/reporting/images/pic02.jpg with 100% similarity]
utils/test/reporting/img/pic03.jpg [moved from utils/test/reporting/images/pic03.jpg with 100% similarity]
utils/test/reporting/img/pic04.jpg [moved from utils/test/reporting/images/pic04.jpg with 100% similarity]
utils/test/reporting/img/pic05.jpg [moved from utils/test/reporting/images/pic05.jpg with 100% similarity]
utils/test/reporting/img/pic06.jpg [moved from utils/test/reporting/images/pic06.jpg with 100% similarity]
utils/test/reporting/img/pic07.jpg [moved from utils/test/reporting/images/pic07.jpg with 100% similarity]
utils/test/reporting/img/pic08.jpg [moved from utils/test/reporting/images/pic08.jpg with 100% similarity]
utils/test/reporting/img/pic09.jpg [moved from utils/test/reporting/images/pic09.jpg with 100% similarity]
utils/test/reporting/img/pic10.jpg [moved from utils/test/reporting/images/pic10.jpg with 100% similarity]
utils/test/reporting/img/pic11.jpg [moved from utils/test/reporting/images/pic11.jpg with 100% similarity]
utils/test/reporting/img/pic12.jpg [moved from utils/test/reporting/images/pic12.jpg with 100% similarity]
utils/test/reporting/img/pic13.jpg [moved from utils/test/reporting/images/pic13.jpg with 100% similarity]
utils/test/reporting/img/pic14.jpg [moved from utils/test/reporting/images/pic14.jpg with 100% similarity]
utils/test/reporting/img/pic15.jpg [moved from utils/test/reporting/images/pic15.jpg with 100% similarity]
utils/test/reporting/img/projectIcon_bottlenecks.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_bottlenecks_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_bottlenecks_60x60.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_dovetail.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_dovetail_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_dovetail_60x60.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_functest.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_functest_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_functest_60x60.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_qtip.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_qtip_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_qtip_60x60.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_storperf.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_storperf_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_storperf_60x60.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_vsperf.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_vsperf_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_vsperf_60x60.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_yardstick.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_yardstick_250x250.png [new file with mode: 0644]
utils/test/reporting/img/projectIcon_yardstick_60x60.png [new file with mode: 0644]
utils/test/reporting/img/weather-clear.png [new file with mode: 0644]
utils/test/reporting/img/weather-few-clouds.png [new file with mode: 0644]
utils/test/reporting/img/weather-overcast.png [new file with mode: 0644]
utils/test/reporting/img/weather-storm.png [new file with mode: 0644]
utils/test/reporting/pages/Gruntfile.js [new file with mode: 0644]
utils/test/reporting/pages/angular.sh [new file with mode: 0755]
utils/test/reporting/pages/app/404.html [new file with mode: 0644]
utils/test/reporting/pages/app/favicon.ico [new file with mode: 0644]
utils/test/reporting/pages/app/images/green.png [new file with mode: 0644]
utils/test/reporting/pages/app/images/green@2x.png [new file with mode: 0644]
utils/test/reporting/pages/app/images/header_one.jpg [new file with mode: 0644]
utils/test/reporting/pages/app/images/logo.png [new file with mode: 0644]
utils/test/reporting/pages/app/images/overview.png [new file with mode: 0644]
utils/test/reporting/pages/app/images/word_map.png [new file with mode: 0644]
utils/test/reporting/pages/app/images/yeoman.png [new file with mode: 0644]
utils/test/reporting/pages/app/index.html [new file with mode: 0644]
utils/test/reporting/pages/app/robots.txt [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/app.config.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/app.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/config.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/config.router.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/controllers/admin.controller.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/controllers/case.controller.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/controllers/main.controller.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/controllers/table.controller.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/controllers/testvisual.controller.js [new file with mode: 0644]
utils/test/reporting/pages/app/scripts/factory/table.factory.js [new file with mode: 0644]
utils/test/reporting/pages/app/styles/custome.css [new file with mode: 0644]
utils/test/reporting/pages/app/views/commons/admin.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/commons/selectTestcase.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/commons/table.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/commons/testCaseList.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/commons/testCaseVisual.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/main.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/modal/testcasedetail.html [new file with mode: 0644]
utils/test/reporting/pages/app/views/testcase.html [new file with mode: 0644]
utils/test/reporting/pages/bower.json [new file with mode: 0644]
utils/test/reporting/pages/package.json [new file with mode: 0644]
utils/test/reporting/pages/test/.jshintrc [new file with mode: 0644]
utils/test/reporting/pages/test/karma.conf.js [new file with mode: 0644]
utils/test/reporting/pages/test/spec/controllers/main.js [new file with mode: 0644]
utils/test/reporting/pages/test/spec/directives/mydirective.js [new file with mode: 0644]
utils/test/reporting/reporting.yaml [new file with mode: 0644]
utils/test/reporting/run_unit_tests.sh [new file with mode: 0755]
utils/test/reporting/setup.py [new file with mode: 0644]
utils/test/reporting/storperf/reporting-status.py [new file with mode: 0644]
utils/test/reporting/storperf/template/index-status-tmpl.html [new file with mode: 0644]
utils/test/reporting/tests/__init__.py [new file with mode: 0644]
utils/test/reporting/tests/unit/__init__.py [new file with mode: 0644]
utils/test/reporting/tests/unit/utils/__init__.py [new file with mode: 0644]
utils/test/reporting/tests/unit/utils/test_utils.py [new file with mode: 0644]
utils/test/reporting/utils/__init__.py [new file with mode: 0644]
utils/test/reporting/utils/reporting_utils.py [new file with mode: 0644]
utils/test/reporting/utils/scenarioResult.py [moved from utils/test/reporting/yardstick/scenarioResult.py with 84% similarity]
utils/test/reporting/yardstick/reporting-status.py
utils/test/reporting/yardstick/reportingConf.py [deleted file]
utils/test/reporting/yardstick/reportingUtils.py [deleted file]
utils/test/reporting/yardstick/scenarios.py
utils/test/reporting/yardstick/template/index-status-tmpl.html
utils/test/testapi/.coveragerc [new file with mode: 0644]
utils/test/testapi/deployment/deploy.py [new file with mode: 0644]
utils/test/testapi/deployment/docker-compose.yml.template [new file with mode: 0644]
utils/test/testapi/docker/Dockerfile
utils/test/testapi/docker/prepare-env.sh
utils/test/testapi/etc/config.ini
utils/test/testapi/htmlize/doc-build.sh [new file with mode: 0644]
utils/test/testapi/htmlize/htmlize.py [new file with mode: 0644]
utils/test/testapi/htmlize/push-doc-artifact.sh [new file with mode: 0644]
utils/test/testapi/install.sh
utils/test/testapi/opnfv_testapi/cmd/server.py
utils/test/testapi/opnfv_testapi/common/config.py
utils/test/testapi/opnfv_testapi/common/message.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/common/raises.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/resources/handlers.py
utils/test/testapi/opnfv_testapi/resources/models.py
utils/test/testapi/opnfv_testapi/resources/pod_handlers.py
utils/test/testapi/opnfv_testapi/resources/pod_models.py
utils/test/testapi/opnfv_testapi/resources/project_handlers.py
utils/test/testapi/opnfv_testapi/resources/project_models.py
utils/test/testapi/opnfv_testapi/resources/result_handlers.py
utils/test/testapi/opnfv_testapi/resources/result_models.py
utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/resources/scenario_models.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/resources/testcase_handlers.py
utils/test/testapi/opnfv_testapi/resources/testcase_models.py
utils/test/testapi/opnfv_testapi/router/url_mappings.py
utils/test/testapi/opnfv_testapi/tests/unit/common/__init__.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/common/noparam.ini [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/common/normal.ini [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/common/nosection.ini [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/common/notboolean.ini [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/common/notint.ini [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/common/test_config.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/fake_pymongo.py
utils/test/testapi/opnfv_testapi/tests/unit/scenario-c1.json [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/scenario-c2.json [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/test_base.py
utils/test/testapi/opnfv_testapi/tests/unit/test_fake_pymongo.py
utils/test/testapi/opnfv_testapi/tests/unit/test_pod.py
utils/test/testapi/opnfv_testapi/tests/unit/test_project.py
utils/test/testapi/opnfv_testapi/tests/unit/test_result.py
utils/test/testapi/opnfv_testapi/tests/unit/test_scenario.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/test_testcase.py
utils/test/testapi/opnfv_testapi/tests/unit/test_token.py [new file with mode: 0644]
utils/test/testapi/opnfv_testapi/tests/unit/test_version.py
utils/test/testapi/run_test.sh
utils/test/testapi/test-requirements.txt
utils/test/testapi/tox.ini [new file with mode: 0644]
utils/test/testapi/update/templates/utils.py
utils/test/testapi/update/test.yml
utils/test/testapi/update/update.yml
utils/test/vnfcatalogue/VNF_Catalogue/.dockerignore [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/Dockerfile [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/README.md [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/app.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/bin/www [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/database.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/docker-compose.yml [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/docker_commands.sh [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/LICENSE [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/README.md [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/wait-for-it.sh [new file with mode: 0755]
utils/test/vnfcatalogue/VNF_Catalogue/migration/Dockerfile [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/migration/migrate.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/migration/package.json [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/migration/schema.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/package.json [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/.DS_Store [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/LICENSE [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/README.md [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/css/materialize.css [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/css/materialize.min.css [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/.DS_Store [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.eot [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.ttf [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff2 [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.eot [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.ttf [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff2 [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.eot [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.ttf [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff2 [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.eot [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.ttf [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff2 [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.eot [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.ttf [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff2 [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/js/materialize.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/js/materialize.min.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/typeahead.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/images/3rd_party/commits.png [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/images/logo.png [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/global.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/mode_edit.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/search_results.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/3rd_party/bootstrap.css [new file with mode: 0755]
utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/project_profile.css [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/search_form.css [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/search_projects.css [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/style.css [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/public/uploads/logo.png [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/add_project.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/add_tag.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/index.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/project_profile.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/search_projects.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/search_tag.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/search_vnf.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/routes/vnf_tag_association.js [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/add_project.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/error.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/index.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/layout.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/project_profile.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/search.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/search_projects.jade [new file with mode: 0644]
utils/test/vnfcatalogue/VNF_Catalogue/views/vnf_tag_association.jade [new file with mode: 0644]
utils/test/vnfcatalogue/cronjobs/README.md [new file with mode: 0644]
utils/test/vnfcatalogue/cronjobs/database.js [new file with mode: 0644]
utils/test/vnfcatalogue/cronjobs/github.js [new file with mode: 0644]
utils/test/vnfcatalogue/helpers/README.md [new file with mode: 0644]
utils/test/vnfcatalogue/helpers/migrate.js [new file with mode: 0644]
utils/test/vnfcatalogue/helpers/schema.js [new file with mode: 0644]

index 96a76e3..eeabaeb 100644 (file)
@@ -5,3 +5,34 @@
 /releng/
 .idea
 *.py[cod]
+
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+.venv/
+venv/
+ENV/
+node_modules/
+.coverage
+=1.3.1
+cover/
+coverage.xml
+nosetests.xml
+testapi_venv/
+.cache
+.tox
+*.retry
+job_output/
diff --git a/INFO b/INFO
index 626637f..069d3d0 100644 (file)
--- a/INFO
+++ b/INFO
@@ -22,6 +22,7 @@ Mei Mei (Huawei, meimei@huawei.com)
 Trevor Bramwell (Linux Foundation, tbramwell@linuxfoundation.org)
 Serena Feng (ZTE, feng.xiaowei@zte.com.cn)
 Yolanda Robla Mota (Red Hat, yroblamo@redhat.com)
+Markos Chandras (SUSE, mchandras@suse.de)
 
 Link to TSC approval of the project: http://ircbot.wl.linuxfoundation.org/meetings/opnfv-meeting/2015/opnfv-meeting.2015-07-14-14.00.html
 Link to TSC voting for removal of Victor Laza as committer: http://meetbot.opnfv.org/meetings/opnfv-meeting/2016/opnfv-meeting.2016-02-16-14.59.html
index b85b132..4d23ade 100644 (file)
@@ -1,9 +1,9 @@
-***************************
+===========================
 Release Engineering Project
-***************************
+===========================
 
 .. toctree::
    :numbered:
    :maxdepth: 2
 
-   opnfv-jjb-usage.rst
+   opnfv-jjb-usage
index 73b31b2..f34833f 100644 (file)
@@ -21,6 +21,14 @@ Make changes::
     To ssh://agardner@gerrit.opnfv.org:29418/releng.git
      * [new branch]      HEAD -> refs/publish/master
 
+Test with tox::
+
+    tox -v -ejjb
+
+Submit the change to gerrit::
+
+    git review -v
+
 Follow the link to gerrit https://gerrit.opnfv.org/gerrit/51 in a few moments
 the verify job will have completed and you will see Verified +1 jenkins-ci in
 the gerrit ui.
@@ -39,11 +47,24 @@ Job Types
 
   * Trigger: **remerge**
 
+* Experimental Job
+
+  * Trigger: **check-experimental**
+
 The verify and merge jobs are retriggerable in Gerrit by simply leaving
 a comment with one of the keywords listed above.
 This is useful in case you need to re-run one of those jobs in case
 if build issues or something changed with the environment.
 
+The experimental jobs are not triggered automatically. You need to leave
+a comment with the keyword list above to trigger it manually. It is useful
+for trying out experimental features.
+
+Note that, experimental jobs `skip vote`_ for verified status, which means
+it will reset the verified status to 0. If you want to keep the verified
+status, use **recheck-experimental** in commit message to trigger both
+verify and experimental jobs.
+
 You can add below persons as reviewers to your patch in order to get it
 reviewed and submitted.
 
@@ -57,6 +78,7 @@ reviewed and submitted.
 * jose.lausuch@ericsson.com
 * koffirodrigue@gmail.com
 * r-mibu@cq.jp.nec.com
+* tbramwell@linuxfoundation.org
 
 Or Add the group releng-contributors
 
@@ -67,3 +89,5 @@ in `releng-jobs.yaml`_.
 
 .. _releng-jobs.yaml:
     https://gerrit.opnfv.org/gerrit/gitweb?p=releng.git;a=blob;f=jjb/releng-jobs.yaml;
+.. _skip vote:
+    https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger#GerritTrigger-SkipVote
index aa10a43..97fea89 100644 (file)
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: 'master'
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
diff --git a/jjb/3rd_party_ci/create-apex-vms.sh b/jjb/3rd_party_ci/create-apex-vms.sh
new file mode 100755 (executable)
index 0000000..0744ac8
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+# clone opnfv sdnvpn repo
+git clone https://gerrit.opnfv.org/gerrit/p/sdnvpn.git $WORKSPACE/sdnvpn
+
+. $WORKSPACE/sdnvpn/odl-pipeline/odl-pipeline-common.sh
+pushd $LIB
+./test_environment.sh --env-number $APEX_ENV_NUMBER --cloner-info $CLONER_INFO --snapshot-disks $SNAPSHOT_DISKS --vjump-hosts $VIRTUAL_JUMPHOSTS
+popd
diff --git a/jjb/3rd_party_ci/download-netvirt-artifact.sh b/jjb/3rd_party_ci/download-netvirt-artifact.sh
new file mode 100755 (executable)
index 0000000..6aea01d
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+ODL_ZIP=distribution-karaf-0.6.0-SNAPSHOT.zip
+
+echo "Attempting to fetch the artifact location from ODL Jenkins"
+CHANGE_DETAILS_URL="https://git.opendaylight.org/gerrit/changes/netvirt~master~$GERRIT_CHANGE_ID/detail"
+# due to limitation with the Jenkins Gerrit Trigger, we need to use Gerrit REST API to get the change details
+ODL_BUILD_JOB_NUM=$(curl -s $CHANGE_DETAILS_URL | grep -Eo 'netvirt-distribution-check-carbon/[0-9]+' | tail -1 | grep -Eo [0-9]+)
+
+NETVIRT_ARTIFACT_URL="https://jenkins.opendaylight.org/releng/job/netvirt-distribution-check-carbon/${ODL_BUILD_JOB_NUM}/artifact/${ODL_ZIP}"
+echo -e "URL to artifact is\n\t$NETVIRT_ARTIFACT_URL"
+
+echo "Downloading the artifact. This could take time..."
+wget -q -O $ODL_ZIP $NETVIRT_ARTIFACT_URL
+if [[ $? -ne 0 ]]; then
+    echo "The artifact does not exist! Probably removed due to ODL Jenkins artifact retention policy."
+    echo "Rerun netvirt-patch-test-current-carbon to get artifact rebuilt."
+    exit 1
+fi
+
+#TODO(trozet) remove this once odl-pipeline accepts zip files
+echo "Converting artifact zip to tar.gz"
+unzip $ODL_ZIP
+tar czf /tmp/${NETVIRT_ARTIFACT} $(echo $ODL_ZIP | sed -n 's/\.zip//p')
+
+echo "Download complete"
+ls -al /tmp/${NETVIRT_ARTIFACT}
diff --git a/jjb/3rd_party_ci/install-netvirt.sh b/jjb/3rd_party_ci/install-netvirt.sh
new file mode 100755 (executable)
index 0000000..ed1a12b
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+SNAP_CACHE=$HOME/snap_cache
+# clone opnfv sdnvpn repo
+git clone https://gerrit.opnfv.org/gerrit/p/sdnvpn.git $WORKSPACE/sdnvpn
+
+if [ ! -f "/tmp/${NETVIRT_ARTIFACT}" ]; then
+  echo "ERROR: /tmp/${NETVIRT_ARTIFACT} specified as NetVirt Artifact, but file does not exist"
+  exit 1
+fi
+
+if [ ! -f "${SNAP_CACHE}/node.yaml" ]; then
+  echo "ERROR: node.yaml pod config missing in ${SNAP_CACHE}"
+  exit 1
+fi
+
+if [ ! -f "${SNAP_CACHE}/id_rsa" ]; then
+  echo "ERROR: id_rsa ssh creds missing in ${SNAP_CACHE}"
+  exit 1
+fi
+
+# TODO (trozet) snapshot should have already been unpacked into cache folder
+# but we really should check the cache here, and not use a single cache folder
+# for when we support multiple jobs on a single slave
+pushd sdnvpn/odl-pipeline/lib > /dev/null
+# FIXME (trozet) remove this once permissions are fixed in sdnvpn repo
+chmod +x odl_reinstaller.sh
+./odl_reinstaller.sh --pod-config ${SNAP_CACHE}/node.yaml \
+  --odl-artifact /tmp/${NETVIRT_ARTIFACT} --ssh-key-file ${SNAP_CACHE}/id_rsa
+popd > /dev/null
diff --git a/jjb/3rd_party_ci/odl-netvirt.yml b/jjb/3rd_party_ci/odl-netvirt.yml
new file mode 100644 (file)
index 0000000..470e433
--- /dev/null
@@ -0,0 +1,229 @@
+- project:
+    name: 'netvirt'
+
+    project: 'netvirt'
+
+    installer: 'netvirt'
+#####################################
+# branch definitions
+#####################################
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+#####################################
+# patch verification phases
+#####################################
+    phase:
+        - 'create-apex-vms':
+            slave-label: 'odl-netvirt-virtual-intel'
+        - 'install-netvirt':
+            slave-label: 'odl-netvirt-virtual-intel'
+        - 'postprocess':
+            slave-label: 'odl-netvirt-virtual-intel'
+#####################################
+# jobs
+#####################################
+    jobs:
+        - 'odl-netvirt-verify-virtual-{stream}'
+        - 'odl-netvirt-verify-virtual-{phase}-{stream}'
+#####################################
+# job templates
+#####################################
+- job-template:
+    name: 'odl-netvirt-verify-virtual-{stream}'
+
+    project-type: multijob
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 5
+            max-per-node: 1
+            option: 'project'
+
+    scm:
+        - git:
+            url: https://gerrit.opnfv.org/gerrit/apex
+            branches:
+                - 'origin/master'
+            timeout: 15
+            wipe-workspace: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - string:
+            name: NETVIRT_ARTIFACT
+            default: distribution-karaf.tar.gz
+        - 'odl-netvirt-virtual-intel-defaults'
+
+    triggers:
+        - gerrit:
+            server-name: 'git.opendaylight.org'
+            trigger-on:
+ #               - comment-added-contains-event:
+ #                   comment-contains-value: 'https://jenkins.opendaylight.org/releng/job/netvirt-patch-test-current-carbon/.*?/ : SUCCESS'
+ #               - comment-added-contains-event:
+ #                   comment-contains-value: 'https://jenkins.opendaylight.org/releng/job/netvirt-patch-test-current-carbon/.*?/ : UNSTABLE'
+                - comment-added-contains-event:
+                    comment-contains-value: 'opnfv-test'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+            readable-message: true
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: create-apex-vms
+            condition: SUCCESSFUL
+            projects:
+                - name: 'odl-netvirt-verify-virtual-create-apex-vms-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_ID=$GERRIT_CHANGE_ID
+                    GERRIT_PATCHSET_NUMBER=$GERRIT_PATCHSET_NUMBER
+                    GERRIT_PATCHSET_REVISION=$GERRIT_PATCHSET_REVISION
+                    NETVIRT_ARTIFACT=$NETVIRT_ARTIFACT
+                    APEX_ENV_NUMBER=$APEX_ENV_NUMBER
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: install-netvirt
+            condition: SUCCESSFUL
+            projects:
+                - name: 'odl-netvirt-verify-virtual-install-netvirt-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_ID=$GERRIT_CHANGE_ID
+                    GERRIT_PATCHSET_NUMBER=$GERRIT_PATCHSET_NUMBER
+                    GERRIT_PATCHSET_REVISION=$GERRIT_PATCHSET_REVISION
+                    NETVIRT_ARTIFACT=$NETVIRT_ARTIFACT
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: functest
+            condition: SUCCESSFUL
+            projects:
+                - name: 'functest-netvirt-virtual-suite-{stream}'
+                  predefined-parameters: |
+                    DEPLOY_SCENARIO=os-odl_l3-nofeature-ha
+                    FUNCTEST_SUITE_NAME=tempest_smoke_serial
+                    RC_FILE_PATH=$HOME/cloner-info/overcloudrc
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: false
+        - multijob:
+            name: postprocess
+            condition: ALWAYS
+            projects:
+                - name: 'odl-netvirt-verify-virtual-postprocess-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_ID=$GERRIT_CHANGE_ID
+                    GERRIT_PATCHSET_NUMBER=$GERRIT_PATCHSET_NUMBER
+                    GERRIT_PATCHSET_REVISION=$GERRIT_PATCHSET_REVISION
+                    NETVIRT_ARTIFACT=$NETVIRT_ARTIFACT
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: false
+
+- job-template:
+    name: 'odl-netvirt-verify-virtual-{phase}-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 5
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'odl-netvirt-verify-virtual-create-apex-vms-.*'
+                - 'odl-netvirt-verify-virtual-install-netvirt-.*'
+                - 'functest-netvirt-virtual-suite-.*'
+                - 'odl-netvirt-verify-virtual-postprocess-.*'
+            block-level: 'NODE'
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    scm:
+        - git:
+            url: https://gerrit.opnfv.org/gerrit/apex
+            branches:
+                - 'origin/master'
+            timeout: 15
+            wipe-workspace: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{slave-label}-defaults'
+        - '{installer}-defaults'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-odl_l2-bgpvpn-noha'
+            description: 'Scenario to deploy and test'
+        - string:
+            name: GS_URL
+            default: artifacts.opnfv.org/apex
+            description: "URL to Google Storage with snapshot artifacts."
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - '{project}-verify-{phase}-builder'
+#####################################
+# builder macros
+#####################################
+- builder:
+    name: 'netvirt-verify-create-apex-vms-builder'
+    builders:
+        - shell:
+            !include-raw: ../apex/apex-snapshot-deploy.sh
+- builder:
+    name: 'netvirt-verify-install-netvirt-builder'
+    builders:
+        - shell:
+            !include-raw: ./download-netvirt-artifact.sh
+        - shell:
+            !include-raw: ./install-netvirt.sh
+- builder:
+    name: 'netvirt-verify-postprocess-builder'
+    builders:
+        - shell:
+            !include-raw: ./postprocess-netvirt.sh
diff --git a/jjb/3rd_party_ci/postprocess-netvirt.sh b/jjb/3rd_party_ci/postprocess-netvirt.sh
new file mode 100755 (executable)
index 0000000..7965142
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+# clone opnfv sdnvpn repo
+git clone https://gerrit.opnfv.org/gerrit/p/sdnvpn.git $WORKSPACE/sdnvpn
+. $WORKSPACE/sdnvpn/odl-pipeline/odl-pipeline-common.sh
+pushd $LIB
+./post_process.sh
+popd
index e3e3f61..b6b2f21 100755 (executable)
@@ -12,6 +12,9 @@ echo
 if echo $BUILD_TAG | grep "apex-verify" 1> /dev/null; then
   export OPNFV_ARTIFACT_VERSION=dev${BUILD_NUMBER}
   export BUILD_ARGS="-r $OPNFV_ARTIFACT_VERSION -c $CACHE_DIRECTORY"
+elif echo $BUILD_TAG | grep "csit" 1> /dev/null; then
+  export OPNFV_ARTIFACT_VERSION=csit${BUILD_NUMBER}
+  export BUILD_ARGS="-r $OPNFV_ARTIFACT_VERSION -c $CACHE_DIRECTORY"
 elif [ "$ARTIFACT_VERSION" == "daily" ]; then
   export OPNFV_ARTIFACT_VERSION=$(date -u +"%Y-%m-%d")
   export BUILD_ARGS="-r $OPNFV_ARTIFACT_VERSION -c $CACHE_DIRECTORY --iso"
@@ -23,12 +26,12 @@ fi
 # start the build
 cd $WORKSPACE/ci
 ./build.sh $BUILD_ARGS
-RPM_VERSION=$(grep Version: $BUILD_DIRECTORY/rpm_specs/opnfv-apex.spec | awk '{ print $2 }')-$(echo $OPNFV_ARTIFACT_VERSION | tr -d '_-')
+RPM_VERSION=$(grep Version: $WORKSPACE/build/rpm_specs/opnfv-apex.spec | awk '{ print $2 }')-$(echo $OPNFV_ARTIFACT_VERSION | tr -d '_-')
 # list the contents of BUILD_OUTPUT directory
-echo "Build Directory is ${BUILD_DIRECTORY}"
+echo "Build Directory is ${BUILD_DIRECTORY}/../.build"
 echo "Build Directory Contents:"
 echo "-------------------------"
-ls -al $BUILD_DIRECTORY
+ls -al ${BUILD_DIRECTORY}/../.build
 
 # list the contents of CACHE directory
 echo "Cache Directory is ${CACHE_DIRECTORY}"
@@ -44,10 +47,10 @@ if ! echo $BUILD_TAG | grep "apex-verify" 1> /dev/null; then
     echo "OPNFV_GIT_URL=$(git config --get remote.origin.url)"
     echo "OPNFV_GIT_SHA1=$(git rev-parse HEAD)"
     echo "OPNFV_ARTIFACT_URL=$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.iso"
-    echo "OPNFV_ARTIFACT_SHA512SUM=$(sha512sum $BUILD_DIRECTORY/release/OPNFV-CentOS-7-x86_64-$OPNFV_ARTIFACT_VERSION.iso | cut -d' ' -f1)"
+    echo "OPNFV_ARTIFACT_SHA512SUM=$(sha512sum $BUILD_DIRECTORY/../.build/release/OPNFV-CentOS-7-x86_64-$OPNFV_ARTIFACT_VERSION.iso | cut -d' ' -f1)"
     echo "OPNFV_SRPM_URL=$GS_URL/opnfv-apex-$RPM_VERSION.src.rpm"
     echo "OPNFV_RPM_URL=$GS_URL/opnfv-apex-$RPM_VERSION.noarch.rpm"
-    echo "OPNFV_RPM_SHA512SUM=$(sha512sum $BUILD_DIRECTORY/noarch/opnfv-apex-$RPM_VERSION.noarch.rpm | cut -d' ' -f1)"
+    echo "OPNFV_RPM_SHA512SUM=$(sha512sum $BUILD_DIRECTORY/../.build/noarch/opnfv-apex-$RPM_VERSION.noarch.rpm | cut -d' ' -f1)"
     echo "OPNFV_BUILD_URL=$BUILD_URL"
   ) > $WORKSPACE/opnfv.properties
 fi
index e21387a..06f7622 100755 (executable)
@@ -3,7 +3,7 @@ set -o errexit
 set -o nounset
 set -o pipefail
 
-APEX_PKGS="common undercloud onos"
+APEX_PKGS="common undercloud" # removed onos for danube
 IPV6_FLAG=False
 
 # log info to console
@@ -15,7 +15,7 @@ if ! rpm -q wget > /dev/null; then
   sudo yum -y install wget
 fi
 
-if [[ $BUILD_DIRECTORY == *verify* ]]; then
+if [[ "$BUILD_DIRECTORY" == *verify* || "$BUILD_DIRECTORY" == *promote* ]]; then
     # Build is from a verify, use local build artifacts (not RPMs)
     cd $WORKSPACE/../${BUILD_DIRECTORY}
     WORKSPACE=$(pwd)
@@ -59,21 +59,42 @@ else
     fi
 fi
 
+# rename odl_l3 to odl only for master
+# this can be removed once all the odl_l3 references
+# are updated to odl after the danube jobs are removed
+if [[ "$BUILD_DIRECTORY" == *master* ]]; then
+    DEPLOY_SCENARIO=${DEPLOY_SCENARIO/odl_l3/odl}
+fi
 if [ -z "$DEPLOY_SCENARIO" ]; then
   echo "Deploy scenario not set!"
   exit 1
+elif [[ "$DEPLOY_SCENARIO" == *gate* ]]; then
+  echo "Detecting Gating scenario..."
+  if [ -z "$GERRIT_EVENT_COMMENT_TEXT" ]; then
+    echo "ERROR: Gate job triggered without comment!"
+    exit 1
+  else
+    DEPLOY_SCENARIO=$(echo ${GERRIT_EVENT_COMMENT_TEXT} | grep start-gate-scenario | grep -Eo 'os-.*$')
+    if [ -z "$DEPLOY_SCENARIO" ]; then
+      echo "ERROR: Unable to detect scenario in Gerrit Comment!"
+      echo "Format of comment to trigger gate should be 'start-gate-scenario: <scenario>'"
+      exit 1
+    else
+      echo "Gate scenario detected: ${DEPLOY_SCENARIO}"
+    fi
+  fi
 fi
 
-# use local build for verify
-if [[ "$BUILD_DIRECTORY" == *verify* ]]; then
+# use local build for verify and promote
+if [[ "$BUILD_DIRECTORY" == *verify* || "$BUILD_DIRECTORY" == *promote* ]]; then
     if [ ! -e "${WORKSPACE}/build/lib" ]; then
       ln -s ${WORKSPACE}/lib ${WORKSPACE}/build/lib
     fi
     DEPLOY_SETTINGS_DIR="${WORKSPACE}/config/deploy"
     NETWORK_SETTINGS_DIR="${WORKSPACE}/config/network"
     DEPLOY_CMD="$(pwd)/deploy.sh"
-    RESOURCES="${WORKSPACE}/build/images/"
-    CONFIG="${WORKSPACE}/build"
+    IMAGES="${WORKSPACE}/.build/"
+    BASE="${WORKSPACE}/build"
     LIB="${WORKSPACE}/lib"
     # Make sure python34 deps are installed
     for dep_pkg in epel-release python34 python34-PyYAML python34-setuptools; do
@@ -108,7 +129,7 @@ if [[ "$BUILD_DIRECTORY" == *verify* ]]; then
 # use RPMs
 else
     # find version of RPM
-    VERSION_EXTENSION=$(echo $(basename $RPM_LIST) | grep -Eo '[0-9]+\.[0-9]+-[0-9]{8}')
+    VERSION_EXTENSION=$(echo $(basename $RPM_LIST) | grep -Eo '[0-9]+\.[0-9]+-([0-9]{8}|[a-z]+-[0-9]\.[0-9]+)')
     # build RPM List which already includes base Apex RPM
     for pkg in ${APEX_PKGS}; do
         RPM_LIST+=" ${RPM_INSTALL_PATH}/opnfv-apex-${pkg}-${VERSION_EXTENSION}.noarch.rpm"
@@ -130,13 +151,13 @@ else
     DEPLOY_CMD=opnfv-deploy
     DEPLOY_SETTINGS_DIR="/etc/opnfv-apex/"
     NETWORK_SETTINGS_DIR="/etc/opnfv-apex/"
-    RESOURCES="/var/opt/opnfv/images"
-    CONFIG="/var/opt/opnfv"
+    IMAGES="/var/opt/opnfv/images"
+    BASE="/var/opt/opnfv"
     LIB="/var/opt/opnfv/lib"
 fi
 
 # set env vars to deploy cmd
-DEPLOY_CMD="CONFIG=${CONFIG} RESOURCES=${RESOURCES} LIB=${LIB} ${DEPLOY_CMD}"
+DEPLOY_CMD="BASE=${BASE} IMAGES=${IMAGES} LIB=${LIB} ${DEPLOY_CMD}"
 
 if [ "$OPNFV_CLEAN" == 'yes' ]; then
   if sudo test -e '/root/inventory/pod_settings.yaml'; then
@@ -144,10 +165,10 @@ if [ "$OPNFV_CLEAN" == 'yes' ]; then
   else
     clean_opts=''
   fi
-  if [[ "$BUILD_DIRECTORY" == *verify* ]]; then
-    sudo CONFIG=${CONFIG} LIB=${LIB} ./clean.sh ${clean_opts}
+  if [[ "$BUILD_DIRECTORY" == *verify* || "$BUILD_DIRECTORY" == *promote* ]]; then
+    sudo BASE=${BASE} LIB=${LIB} ./clean.sh ${clean_opts}
   else
-    sudo CONFIG=${CONFIG} LIB=${LIB} opnfv-clean ${clean_opts}
+    sudo BASE=${BASE} LIB=${LIB} opnfv-clean ${clean_opts}
   fi
 fi
 
@@ -166,21 +187,32 @@ fi
 
 if [[ "$JOB_NAME" == *virtual* ]]; then
   # settings for virtual deployment
-  if [ "$IPV6_FLAG" == "True" ]; then
-    NETWORK_FILE="${NETWORK_SETTINGS_DIR}/network_settings_v6.yaml"
-  else
-    NETWORK_FILE="${NETWORK_SETTINGS_DIR}/network_settings.yaml"
-  fi
   DEPLOY_CMD="${DEPLOY_CMD} -v"
+  if [[ "${DEPLOY_SCENARIO}" =~ fdio|ovs ]]; then
+    DEPLOY_CMD="${DEPLOY_CMD} --virtual-default-ram 12 --virtual-compute-ram 7"
+  fi
+  if [[ "$JOB_NAME" == *csit* ]]; then
+    DEPLOY_CMD="${DEPLOY_CMD} -e csit-environment.yaml"
+  fi
+  if [[ "$JOB_NAME" == *promote* ]]; then
+    DEPLOY_CMD="${DEPLOY_CMD} --virtual-computes 2"
+  fi
 else
   # settings for bare metal deployment
-  if [ "$IPV6_FLAG" == "True" ]; then
-    NETWORK_FILE="/root/network/network_settings_v6.yaml"
-  else
-    NETWORK_FILE="/root/network/network_settings.yaml"
-  fi
+  NETWORK_SETTINGS_DIR="/root/network"
   INVENTORY_FILE="/root/inventory/pod_settings.yaml"
 
+# (trozet) According to FDS folks uio_pci_generic works with UCS-B
+# and there appears to be a bug with vfio-pci
+  # if fdio on baremetal, then we are using UCS enic and
+  # need to use vfio-pci instead of uio generic
+#  if [[ "$DEPLOY_SCENARIO" == *fdio* ]]; then
+#    TMP_DEPLOY_FILE="${WORKSPACE}/${DEPLOY_SCENARIO}.yaml"
+#    cp -f ${DEPLOY_FILE} ${TMP_DEPLOY_FILE}
+#    sed -i 's/^\(\s*uio-driver:\).*$/\1 vfio-pci/g' ${TMP_DEPLOY_FILE}
+#    DEPLOY_FILE=${TMP_DEPLOY_FILE}
+#  fi
+
   if ! sudo test -e "$INVENTORY_FILE"; then
     echo "ERROR: Required settings file missing: Inventory settings file ${INVENTORY_FILE}"
     exit 1
@@ -189,6 +221,14 @@ else
   DEPLOY_CMD="${DEPLOY_CMD} -i ${INVENTORY_FILE}"
 fi
 
+if [ "$IPV6_FLAG" == "True" ]; then
+  NETWORK_FILE="${NETWORK_SETTINGS_DIR}/network_settings_v6.yaml"
+elif echo ${DEPLOY_SCENARIO} | grep fdio; then
+  NETWORK_FILE="${NETWORK_SETTINGS_DIR}/network_settings_vpp.yaml"
+else
+  NETWORK_FILE="${NETWORK_SETTINGS_DIR}/network_settings.yaml"
+fi
+
 # Check that network settings file exists
 if ! sudo test -e "$NETWORK_FILE"; then
   echo "ERROR: Required settings file missing: Network Settings file ${NETWORK_FILE}"
@@ -198,6 +238,16 @@ fi
 # start deployment
 sudo ${DEPLOY_CMD} -d ${DEPLOY_FILE} -n ${NETWORK_FILE} --debug
 
+if [[ "$JOB_NAME" == *csit* ]]; then
+  echo "CSIT job: setting host route for floating ip routing"
+  # csit route to allow docker container to reach floating ips
+  UNDERCLOUD=$(sudo virsh domifaddr undercloud | grep -Eo "[0-9\.]+{3}[0-9]+")
+  if sudo route | grep 192.168.37.128 > /dev/null; then
+    sudo route del -net 192.168.37.128 netmask 255.255.255.128
+  fi
+  sudo route add -net 192.168.37.128 netmask 255.255.255.128 gw ${UNDERCLOUD}
+fi
+
 echo
 echo "--------------------------------------------------------"
 echo "Done!"
diff --git a/jjb/apex/apex-snapshot-create.sh b/jjb/apex/apex-snapshot-create.sh
new file mode 100644 (file)
index 0000000..b2a3944
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+##############################################################################
+# Copyright (c) 2016 Tim Rozet (Red Hat) and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null -o LogLevel=error)
+SNAP_TYPE=$(echo ${JOB_NAME} | sed -n 's/^apex-\(.\+\)-promote.*$/\1/p')
+
+echo "Creating Apex snapshot..."
+echo "-------------------------"
+echo
+
+# create tmp directory
+tmp_dir=$(pwd)/.tmp
+mkdir -p ${tmp_dir}
+
+# TODO(trozet) remove this after fix goes in for tripleo_inspector to copy these
+pushd ${tmp_dir} > /dev/null
+echo "Copying overcloudrc and ssh key from Undercloud..."
+# Store overcloudrc
+UNDERCLOUD=$(sudo virsh domifaddr undercloud | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
+sudo scp ${SSH_OPTIONS[@]} stack@${UNDERCLOUD}:overcloudrc ./
+# Copy out ssh key of stack from undercloud
+sudo scp ${SSH_OPTIONS[@]} stack@${UNDERCLOUD}:.ssh/id_rsa ./
+popd > /dev/null
+
+echo "Gathering introspection information"
+git clone https://gerrit.opnfv.org/gerrit/sdnvpn.git
+pushd sdnvpn/odl-pipeline/lib > /dev/null
+sudo ./tripleo_introspector.sh --out-file ${tmp_dir}/node.yaml
+popd > /dev/null
+sudo rm -rf sdnvpn
+
+echo "Shutting down nodes"
+# Shut down nodes
+nodes=$(sudo virsh list | grep -Eo "baremetal[0-9]")
+for node in $nodes; do
+  sudo virsh shutdown ${node} --mode acpi
+done
+
+for node in $nodes; do
+  count=0
+  while [ "$count" -lt 10 ]; do
+    sleep 10
+    if sudo virsh list | grep ${node}; then
+       echo "Waiting for $node to shutdown, try $count"
+    else
+       break
+    fi
+    count=$((count+1))
+  done
+
+  if [ "$count" -ge 10 ]; then
+    echo "Node $node failed to shutdown"
+    exit 1
+  fi
+done
+
+pushd ${tmp_dir} > /dev/null
+echo "Gathering virsh definitions"
+# copy qcow2s, virsh definitions
+for node in $nodes; do
+  sudo cp -f /var/lib/libvirt/images/${node}.qcow2 ./
+  sudo virsh dumpxml ${node} > ${node}.xml
+done
+
+# copy virsh net definitions
+for net in admin api external storage tenant; do
+  sudo virsh net-dumpxml ${net} > ${net}.xml
+done
+
+sudo chown jenkins-ci:jenkins-ci *
+
+# tar up artifacts
+DATE=`date +%Y-%m-%d`
+tar czf ../apex-${SNAP_TYPE}-snap-${DATE}.tar.gz .
+popd > /dev/null
+sudo rm -rf ${tmp_dir}
+echo "Snapshot saved as apex-${SNAP_TYPE}-snap-${DATE}.tar.gz"
+
+# update opnfv properties file
+if [ "$SNAP_TYPE" == 'csit' ]; then
+  curl -O -L http://$GS_URL/snapshot.properties
+  sed -i '/^OPNFV_SNAP_URL=/{h;s#=.*#='${GS_URL}'/apex-csit-snap-'${DATE}'.tar.gz#};${x;/^$/{s##OPNFV_SNAP_URL='${GS_URL}'/apex-csit-snap-'${DATE}'.tar.gz#;H};x}' snapshot.properties
+  snap_sha=$(sha512sum apex-csit-snap-${DATE}.tar.gz | cut -d' ' -f1)
+  sed -i '/^OPNFV_SNAP_SHA512SUM=/{h;s/=.*/='${snap_sha}'/};${x;/^$/{s//OPNFV_SNAP_SHA512SUM='${snap_sha}'/;H};x}' snapshot.properties
+  echo "OPNFV_SNAP_URL=$GS_URL/apex-csit-snap-${DATE}.tar.gz"
+  echo "OPNFV_SNAP_SHA512SUM=$(sha512sum apex-csit-snap-${DATE}.tar.gz | cut -d' ' -f1)"
+  echo "Updated properties file: "
+  cat snapshot.properties
+fi
diff --git a/jjb/apex/apex-snapshot-deploy.sh b/jjb/apex/apex-snapshot-deploy.sh
new file mode 100644 (file)
index 0000000..06c0023
--- /dev/null
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+##############################################################################
+# Copyright (c) 2016 Tim Rozet (Red Hat) and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null -o LogLevel=error)
+SNAP_CACHE=$HOME/snap_cache
+
+
+echo "Deploying Apex snapshot..."
+echo "--------------------------"
+echo
+
+echo "Cleaning server"
+pushd ci > /dev/null
+sudo BASE=../build/ LIB=../lib ./clean.sh
+popd > /dev/null
+
+echo "Downloading latest snapshot properties file"
+if ! wget -O $WORKSPACE/opnfv.properties http://$GS_URL/snapshot.properties; then
+  echo "ERROR: Unable to find snapshot.properties at ${GS_URL}...exiting"
+  exit 1
+fi
+
+# find latest check sum
+latest_snap_checksum=$(cat opnfv.properties | grep OPNFV_SNAP_SHA512SUM | awk -F "=" '{print $2}')
+if [ -z "$latest_snap_checksum" ]; then
+  echo "ERROR: checksum of latest snapshot from snapshot.properties is null!"
+  exit 1
+fi
+
+local_snap_checksum=""
+
+# check snap cache directory exists
+# if snapshot cache exists, find the checksum
+if [ -d "$SNAP_CACHE" ]; then
+  latest_snap=$(ls ${SNAP_CACHE} | grep tar.gz | tail -n 1)
+  if [ -n "$latest_snap" ]; then
+    local_snap_checksum=$(sha512sum ${SNAP_CACHE}/${latest_snap} | cut -d' ' -f1)
+  fi
+else
+  mkdir -p ${SNAP_CACHE}
+fi
+
+# compare check sum and download latest snap if not up to date
+if [ "$local_snap_checksum" != "$latest_snap_checksum" ]; then
+  snap_url=$(cat opnfv.properties | grep OPNFV_SNAP_URL | awk -F "=" '{print $2}')
+  if [ -z "$snap_url" ]; then
+    echo "ERROR: Snap URL from snapshot.properties is null!"
+    exit 1
+  fi
+  echo "INFO: SHA mismatch, will download latest snapshot"
+  # wipe cache
+  rm -rf ${SNAP_CACHE}/*
+  wget --directory-prefix=${SNAP_CACHE}/ ${snap_url}
+  snap_tar=$(basename ${snap_url})
+else
+  snap_tar=${latest_snap}
+fi
+
+echo "INFO: Snapshot to be used is ${snap_tar}"
+
+# move to snap cache dir and unpack
+pushd ${SNAP_CACHE} > /dev/null
+tar xvf ${snap_tar}
+
+# create each network
+virsh_networks=$(ls *.xml | grep -v baremetal)
+
+if [ -z "$virsh_networks" ]; then
+  echo "ERROR: no virsh networks found in snapshot unpack"
+  exit 1
+fi
+
+echo "Checking overcloudrc"
+if ! stat overcloudrc; then
+  echo "ERROR: overcloudrc does not exist in snap unpack"
+  exit 1
+fi
+
+for network_def in ${virsh_networks}; do
+  sudo virsh net-create ${network_def}
+  network=$(echo ${network_def} | awk -F '.' '{print $1}')
+  if ! sudo virsh net-list | grep ${network}; then
+    sudo virsh net-start ${network}
+  fi
+  echo "Checking if OVS bridge is missing for network: ${network}"
+  if ! sudo ovs-vsctl show | grep "br-${network}"; then
+    sudo ovs-vsctl add-br br-${network}
+    echo "OVS Bridge created: br-${network}"
+    if [ "br-${network}" == 'br-admin' ]; then
+      echo "Configuring IP 192.0.2.99 on br-admin"
+      sudo ip addr add  192.0.2.99/24 dev br-admin
+      sudo ip link set up dev br-admin
+    elif [ "br-${network}" == 'br-external' ]; then
+      echo "Configuring IP 192.168.37.1 on br-external"
+      sudo ip addr add  192.168.37.1/24 dev br-external
+      sudo ip link set up dev br-external
+      # Routes for admin network
+      # The overcloud controller is multi-homed and will fail to respond
+      # to traffic from the functest container due to reverse-path-filtering
+      # This route allows reverse traffic, by forcing admin network destined
+      # traffic through the external network for controller IPs only.
+      # Compute nodes have no ip on external interfaces.
+      controller_ips=$(cat overcloudrc | grep -Eo "192.0.2.[0-9]+")
+      for ip in $controller_ips; do
+        sudo ip route add ${ip}/32 dev br-external
+      done
+    fi
+  fi
+done
+
+echo "Virsh networks up: $(sudo virsh net-list)"
+echo "Bringing up Overcloud VMs..."
+virsh_vm_defs=$(ls baremetal*.xml)
+
+if [ -z "$virsh_vm_defs" ]; then
+  echo "ERROR: no virsh VMs found in snapshot unpack"
+  exit 1
+fi
+
+for node_def in ${virsh_vm_defs}; do
+  sudo virsh define ${node_def}
+  node=$(echo ${node_def} | awk -F '.' '{print $1}')
+  sudo cp -f ${node}.qcow2 /var/lib/libvirt/images/
+  sudo virsh start ${node}
+  echo "Node: ${node} started"
+done
+
+# copy overcloudrc for functest
+mkdir -p $HOME/cloner-info
+cp -f overcloudrc $HOME/cloner-info/
+
+admin_controller_ip=$(cat overcloudrc | grep -Eo -m 1 "192.0.2.[0-9]+")
+netvirt_url="http://${admin_controller_ip}:8081/restconf/operational/network-topology:network-topology/topology/netvirt:1"
+
+source overcloudrc
+counter=1
+while [ "$counter" -le 10 ]; do
+  if curl --fail --silent ${admin_controller_ip}:80 > /dev/null; then
+    echo "Overcloud Horizon is up...Checking if OpenDaylight NetVirt is up..."
+    if curl --fail --silent -u admin:admin ${netvirt_url} > /dev/null; then
+      echo "OpenDaylight is up.  Overcloud deployment complete"
+      exit 0
+    else
+      echo "OpenDaylight not yet up, try ${counter}"
+    fi
+  else
+    echo "Horizon/Apache not yet up, try ${counter}"
+  fi
+  counter=$((counter+1))
+  sleep 60
+done
+
+echo "ERROR: Deployment not up after 10 minutes...exiting."
+exit 1
index 5c43417..12cb862 100755 (executable)
@@ -9,7 +9,7 @@ echo
 
 
 pushd ci/ > /dev/null
-sudo CONFIG="${WORKSPACE}/build" LIB="${WORKSPACE}/lib" ./clean.sh
+sudo BASE="${WORKSPACE}/build" LIB="${WORKSPACE}/lib" ./clean.sh
 ./test.sh
 popd
 
index f54e4c5..c2de7d7 100755 (executable)
@@ -11,6 +11,8 @@ echo
 # source the opnfv.properties to get ARTIFACT_VERSION
 source $WORKSPACE/opnfv.properties
 
+BUILD_DIRECTORY=${WORKSPACE}/.build
+
 # clone releng repository
 echo "Cloning releng repository..."
 [ -d releng ] && rm -rf releng
@@ -49,13 +51,13 @@ echo "ISO Upload Complete!"
 RPM_INSTALL_PATH=$BUILD_DIRECTORY/noarch
 RPM_LIST=$RPM_INSTALL_PATH/$(basename $OPNFV_RPM_URL)
 VERSION_EXTENSION=$(echo $(basename $OPNFV_RPM_URL) | sed 's/opnfv-apex-//')
-for pkg in common undercloud onos; do
+for pkg in common undercloud; do # removed onos for danube
     RPM_LIST+=" ${RPM_INSTALL_PATH}/opnfv-apex-${pkg}-${VERSION_EXTENSION}"
 done
 SRPM_INSTALL_PATH=$BUILD_DIRECTORY
 SRPM_LIST=$SRPM_INSTALL_PATH/$(basename $OPNFV_SRPM_URL)
 VERSION_EXTENSION=$(echo $(basename $OPNFV_SRPM_URL) | sed 's/opnfv-apex-//')
-for pkg in common undercloud onos; do
+for pkg in common undercloud; do # removed onos for danube
     SRPM_LIST+=" ${SRPM_INSTALL_PATH}/opnfv-apex-${pkg}-${VERSION_EXTENSION}"
 done
 }
@@ -71,7 +73,20 @@ gsutil cp $WORKSPACE/opnfv.properties gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION
 gsutil cp $WORKSPACE/opnfv.properties gs://$GS_URL/latest.properties > gsutil.latest.log
 }
 
-if gpg2 --list-keys | grep "opnfv-helpdesk@rt.linuxfoundation.org"; then
+uploadsnap () {
+  # Uploads snapshot artifact and updated properties file
+  echo "Uploading snapshot artifacts"
+  SNAP_TYPE=$(echo ${JOB_NAME} | sed -n 's/^apex-\(.\+\)-promote.*$/\1/p')
+  gsutil cp $WORKSPACE/apex-${SNAP_TYPE}-snap-`date +%Y-%m-%d`.tar.gz gs://$GS_URL/ > gsutil.iso.log
+  if [ "$SNAP_TYPE" == 'csit' ]; then
+    gsutil cp $WORKSPACE/snapshot.properties gs://$GS_URL/snapshot.properties > gsutil.latest.log
+  fi
+  echo "Upload complete for Snapshot"
+}
+
+if echo $WORKSPACE | grep promote > /dev/null; then
+  uploadsnap
+elif gpg2 --list-keys | grep "opnfv-helpdesk@rt.linuxfoundation.org"; then
   echo "Signing Key avaliable"
   signiso
   uploadiso
index 89965d7..e7982ba 100644 (file)
@@ -2,6 +2,7 @@
     name: apex
     jobs:
         - 'apex-verify-{stream}'
+        - 'apex-verify-gate-{stream}'
         - 'apex-verify-unit-tests-{stream}'
         - 'apex-runner-{platform}-{scenario}-{stream}'
         - 'apex-runner-cperf-{stream}'
@@ -9,6 +10,8 @@
         - 'apex-deploy-virtual-{scenario}-{stream}'
         - 'apex-deploy-baremetal-{scenario}-{stream}'
         - 'apex-daily-{stream}'
+        - 'apex-csit-promote-daily-{stream}'
+        - 'apex-fdio-promote-daily-{stream}'
 
     # stream:    branch with - in place of / (eg. stable-arno)
     # branch:    branch (eg. stable/arno)
         - master:
             branch: 'master'
             gs-pathname: ''
-            block-stream: 'colorado'
             slave: 'lf-pod1'
             verify-slave: 'apex-verify-master'
             daily-slave: 'apex-daily-master'
-        - colorado:
-            branch: 'stable/colorado'
-            gs-pathname: '/colorado'
-            block-stream: 'master'
+        - danube:
+            branch: 'stable/danube'
+            gs-pathname: '/danube'
             slave: 'lf-pod1'
-            verify-slave: 'apex-verify-colorado'
-            daily-slave: 'apex-daily-colorado'
-            disabled: false
-
-    stream1:
-        - master:
-            branch: 'master'
-            gs-pathname: ''
-            block-stream: 'colorado'
-            slave: 'lf-pod1'
-            verify-slave: 'apex-verify-master'
-            daily-slave: 'apex-daily-master'
-
-    stream2:
-        - colorado:
-            branch: 'stable/colorado'
-            gs-pathname: '/colorado'
-            block-stream: 'master'
-            slave: 'lf-pod1'
-            verify-slave: 'apex-verify-colorado'
-            daily-slave: 'apex-daily-colorado'
-            disabled: false
+            verify-slave: 'apex-verify-danube'
+            daily-slave: 'apex-daily-danube'
 
     project: 'apex'
 
          - 'os-nosdn-nofeature-ha'
          - 'os-nosdn-nofeature-ha-ipv6'
          - 'os-nosdn-ovs-noha'
+         - 'os-nosdn-ovs-ha'
          - 'os-nosdn-fdio-noha'
-         - 'os-odl_l2-nofeature-ha'
-         - 'os-odl_l2-bgpvpn-ha'
+         - 'os-nosdn-fdio-ha'
+         - 'os-nosdn-kvm-ha'
+         - 'os-nosdn-kvm-noha'
          - 'os-odl_l2-fdio-noha'
+         - 'os-odl_l2-fdio-ha'
+         - 'os-odl_l2-netvirt_gbp_fdio-noha'
          - 'os-odl_l2-sfc-noha'
+         - 'os-odl_l3-nofeature-noha'
          - 'os-odl_l3-nofeature-ha'
+         - 'os-odl_l3-ovs-noha'
+         - 'os-odl_l3-ovs-ha'
+         - 'os-odl-bgpvpn-ha'
+         - 'os-odl-gluon-noha'
+         - 'os-odl_l3-fdio-noha'
+         - 'os-odl_l3-fdio-ha'
+         - 'os-odl_l3-fdio_dvr-noha'
+         - 'os-odl_l3-fdio_dvr-ha'
+         - 'os-odl_l3-csit-noha'
          - 'os-onos-nofeature-ha'
-         - 'os-onos-sfc-ha'
-         - 'os-ocl-nofeature-ha'
+         - 'os-ovn-nofeature-noha'
+         - 'gate'
 
     platform:
          - 'baremetal'
@@ -82,7 +77,6 @@
             gs-pathname: '{gs-pathname}'
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_BASE
             description: "Used for overriding the GIT URL coming from parameters macro."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                   - compare-type: ANT
                     pattern: 'tests/**'
     properties:
+        - logrotate-default
         - throttle:
             max-per-node: 1
             max-total: 10
             gs-pathname: '{gs-pathname}'
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_BASE
             description: "Used for overriding the GIT URL coming from parameters macro."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                     pattern: 'config/**'
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             block-level: 'NODE'
         - 'apex-unit-test'
         - 'apex-build'
         - trigger-builds:
-          - project: 'apex-deploy-virtual-os-nosdn-nofeature-ha-{stream}'
+          - project: 'apex-deploy-virtual-os-odl_l3-nofeature-ha-{stream}'
             predefined-parameters: |
               BUILD_DIRECTORY=apex-verify-{stream}
               OPNFV_CLEAN=yes
         - trigger-builds:
           - project: 'functest-apex-{verify-slave}-suite-{stream}'
             predefined-parameters: |
-              DEPLOY_SCENARIO=os-nosdn-nofeature-ha
+              DEPLOY_SCENARIO=os-odl_l3-nofeature-ha
               FUNCTEST_SUITE_NAME=healthcheck
             block: true
             same-node: true
+        - 'apex-workspace-cleanup'
+
+# Verify Scenario Gate
+- job-template:
+    name: 'apex-verify-gate-{stream}'
+
+    node: '{verify-slave}'
+
+    concurrent: true
+
+    parameters:
+        - apex-parameter:
+            gs-pathname: '{gs-pathname}'
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: "Used for overriding the GIT URL coming from parameters macro."
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - comment-added-contains-event:
+                    comment-contains-value: '^Patch Set [0-9]+: Code-Review\+2.*start-gate-scenario:.*'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: 'apex'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: 'ci/**'
+                  - compare-type: ANT
+                    pattern: 'build/**'
+                  - compare-type: ANT
+                    pattern: 'lib/**'
+                  - compare-type: ANT
+                    pattern: 'config/**'
+
+    properties:
+        - logrotate-default
+        - build-blocker:
+            use-build-blocker: true
+            block-level: 'NODE'
+            blocking-jobs:
+                - 'apex-daily.*'
+                - 'apex-deploy.*'
+                - 'apex-build.*'
+                - 'apex-runner.*'
+                - 'apex-verify.*'
+        - throttle:
+            max-per-node: 1
+            max-total: 10
+            option: 'project'
+
+    builders:
+        - 'apex-build'
         - trigger-builds:
-          - project: 'apex-deploy-virtual-os-odl_l2-nofeature-ha-{stream}'
+          - project: 'apex-deploy-virtual-gate-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-verify-{stream}
+              BUILD_DIRECTORY=apex-verify-gate-{stream}
               OPNFV_CLEAN=yes
+            current-parameters: true
             git-revision: false
             block: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{verify-slave}-suite-{stream}'
             predefined-parameters: |
-              DEPLOY_SCENARIO=os-odl_l2-nofeature-ha
+              DEPLOY_SCENARIO=os-nosdn-nofeature-ha
               FUNCTEST_SUITE_NAME=healthcheck
             block: true
             same-node: true
             gs-pathname: '{gs-pathname}'
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_BASE
             description: "Used for overriding the GIT URL coming from parameters macro."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             blocking-jobs:
                 - 'apex-daily.*'
                 - 'apex-verify.*'
+                - 'apex-.*-promote.*'
 
     builders:
         - trigger-builds:
             gs-pathname: '{gs-pathname}'
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_BASE
             description: "Used for overriding the GIT URL coming from parameters macro."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: false
             block-level: 'NODE'
 
     builders:
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-odl_l2-nofeature-ha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl_l3-nofeature-noha-{stream}'
             predefined-parameters:
               OPNFV_CLEAN=yes
             git-revision: false
             block: true
             same-node: true
         - trigger-builds:
-          - project: 'cperf-apex-intel-pod2-daily-{stream}'
+          - project: 'cperf-apex-intel-pod2-daily-master'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-nofeature-ha
+              DEPLOY_SCENARIO=os-odl_l3-nofeature-noha
             block: true
             same-node: true
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - apex-parameter:
             gs-pathname: '{gs-pathname}'
-        - gerrit-parameter:
-            branch: '{branch}'
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: "Used for overriding the GIT URL coming from parameters macro."
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             block-level: 'NODE'
         - trigger-builds:
           - project: 'apex-deploy-virtual-os-nosdn-nofeature-noha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: false
             same-node: true
     disabled: false
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - apex-parameter:
             gs-pathname: '{gs-pathname}'
         - string:
             description: "Use yes in lower case to invoke clean. Indicates if the deploy environment should be cleaned before deployment"
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             block-level: 'NODE'
     disabled: false
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - apex-parameter:
             gs-pathname: '{gs-pathname}'
         - string:
             description: "Scenario to deploy with."
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             block-level: 'NODE'
     disabled: false
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - apex-parameter:
             gs-pathname: '{gs-pathname}'
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             block-level: 'NODE'
                 - 'apex-deploy.*'
                 - 'apex-build.*'
                 - 'apex-runner.*'
+                - 'apex-.*-promote.*'
 
     triggers:
         - 'apex-{stream}'
         - trigger-builds:
           - project: 'apex-deploy-baremetal-os-nosdn-nofeature-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
                 build-step-failure-threshold: 'never'
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
+        # 1.dovetail only master by now, not sync with A/B/C branches
+        # 2.here the stream means the SUT stream, dovetail stream is defined in its own job
+        # 3.only debug testsuite here(includes basic testcase,
+        #   i.e. one tempest smoke ipv6, two vping from functest)
+        # 4.not used for release criteria or compliance,
+        #   only to debug the dovetail tool bugs with apex
+        #- trigger-builds:
+        #    - project: 'dovetail-apex-{slave}-debug-{stream}'
+        #      current-parameters: false
+        #      predefined-parameters:
+        #        DEPLOY_SCENARIO=os-nosdn-nofeature-ha
+        #      block: true
+        #      same-node: true
+        #      block-thresholds:
+        #        build-step-failure-threshold: 'never'
+        #        failure-threshold: 'never'
+        #        unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-odl_l2-nofeature-ha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl_l3-nofeature-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-nofeature-ha
+              DEPLOY_SCENARIO=os-odl_l3-nofeature-ha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-nofeature-ha
+              DEPLOY_SCENARIO=os-odl_l3-nofeature-ha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-odl_l3-nofeature-ha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl-bgpvpn-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l3-nofeature-ha
+              DEPLOY_SCENARIO=os-odl-bgpvpn-ha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l3-nofeature-ha
+              DEPLOY_SCENARIO=os-odl-bgpvpn-ha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-onos-nofeature-ha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl-gluon-noha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-onos-nofeature-ha
+              DEPLOY_SCENARIO=os-odl-gluon-noha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-onos-nofeature-ha
+              DEPLOY_SCENARIO=os-odl-gluon-noha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-odl_l2-bgpvpn-ha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl_l2-fdio-noha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-bgpvpn-ha
+              DEPLOY_SCENARIO=os-odl_l2-fdio-noha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-bgpvpn-ha
+              DEPLOY_SCENARIO=os-odl_l2-fdio-noha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-onos-sfc-ha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl_l2-fdio-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-onos-sfc-ha
+              DEPLOY_SCENARIO=os-odl_l2-fdio-ha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-onos-sfc-ha
+              DEPLOY_SCENARIO=os-odl_l2-fdio-ha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-odl_l2-sfc-noha-{stream}'
+          - project: 'apex-deploy-baremetal-os-nosdn-kvm-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-sfc-noha
+              DEPLOY_SCENARIO=os-nosdn-kvm-ha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-sfc-noha
+              DEPLOY_SCENARIO=os-nosdn-kvm-ha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-odl_l2-fdio-noha-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl_l3-fdio-noha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-fdio-noha
+              DEPLOY_SCENARIO=os-odl_l3-fdio-noha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-odl_l2-fdio-noha
+              DEPLOY_SCENARIO=os-odl_l3-fdio-noha
+            block: true
+            same-node: true
+            block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+        - trigger-builds:
+          - project: 'apex-deploy-baremetal-os-nosdn-fdio-ha-{stream}'
+            predefined-parameters: |
+              BUILD_DIRECTORY=apex-build-{stream}/.build
+              OPNFV_CLEAN=yes
+            git-revision: true
+            same-node: true
+            block-thresholds:
+                build-step-failure-threshold: 'never'
+            block: true
+        - trigger-builds:
+          - project: 'functest-apex-{daily-slave}-daily-{stream}'
+            predefined-parameters:
+              DEPLOY_SCENARIO=os-nosdn-fdio-ha
+            block: true
+            same-node: true
+            block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+        - trigger-builds:
+          - project: 'yardstick-apex-{slave}-daily-{stream}'
+            predefined-parameters:
+              DEPLOY_SCENARIO=os-nosdn-fdio-ha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-nosdn-fdio-noha-{stream}'
+          - project: 'apex-deploy-baremetal-os-nosdn-ovs-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-nosdn-fdio-noha
+              DEPLOY_SCENARIO=os-nosdn-ovs-ha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-nosdn-fdio-noha
+              DEPLOY_SCENARIO=os-nosdn-ovs-ha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
         - trigger-builds:
-          - project: 'apex-deploy-virtual-os-nosdn-nofeature-ha-ipv6-{stream}'
+          - project: 'apex-deploy-baremetal-os-odl_l3-ovs-ha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
                 build-step-failure-threshold: 'never'
             block: true
         - trigger-builds:
-          - project: 'apex-deploy-baremetal-os-nosdn-ovs-noha-{stream}'
+          - project: 'functest-apex-{daily-slave}-daily-{stream}'
+            predefined-parameters:
+              DEPLOY_SCENARIO=os-odl_l3-ovs-ha
+            block: true
+            same-node: true
+            block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+        - trigger-builds:
+          - project: 'yardstick-apex-{slave}-daily-{stream}'
+            predefined-parameters:
+              DEPLOY_SCENARIO=os-odl_l3-ovs-ha
+            block: true
+            same-node: true
+            block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+        - trigger-builds:
+          - project: 'apex-deploy-baremetal-os-ovn-nofeature-noha-{stream}'
             predefined-parameters: |
-              BUILD_DIRECTORY=apex-build-{stream}/build
+              BUILD_DIRECTORY=apex-build-{stream}/.build
               OPNFV_CLEAN=yes
             git-revision: true
             same-node: true
         - trigger-builds:
           - project: 'functest-apex-{daily-slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-nosdn-ovs-noha
+              DEPLOY_SCENARIO=os-ovn-nofeature-noha
             block: true
             same-node: true
             block-thresholds:
         - trigger-builds:
           - project: 'yardstick-apex-{slave}-daily-{stream}'
             predefined-parameters:
-              DEPLOY_SCENARIO=os-nosdn-ovs-noha
+              DEPLOY_SCENARIO=os-ovn-nofeature-noha
             block: true
             same-node: true
             block-thresholds:
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
 
+# CSIT promote
+- job-template:
+    name: 'apex-csit-promote-daily-{stream}'
+
+    # Job template for promoting CSIT Snapshots
+    #
+    # Required Variables:
+    #     stream:    branch with - in place of / (eg. stable)
+    #     branch:    branch (eg. stable)
+    node: '{daily-slave}'
+
+    disabled: false
+
+    scm:
+        - git-scm
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - apex-parameter:
+            gs-pathname: '{gs-pathname}'
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            block-level: 'NODE'
+            blocking-jobs:
+                - 'apex-verify.*'
+                - 'apex-deploy.*'
+                - 'apex-build.*'
+                - 'apex-runner.*'
+                - 'apex-daily.*'
+
+    triggers:
+        - timed: '0 12 * * 0'
+
+    builders:
+        - 'apex-build'
+        - trigger-builds:
+          - project: 'apex-deploy-virtual-os-odl_l3-csit-noha-{stream}'
+            predefined-parameters: |
+              BUILD_DIRECTORY=apex-csit-promote-daily-{stream}
+              OPNFV_CLEAN=yes
+            git-revision: false
+            block: true
+            same-node: true
+        - trigger-builds:
+          - project: 'functest-apex-{daily-slave}-suite-{stream}'
+            predefined-parameters: |
+              DEPLOY_SCENARIO=os-odl_l3-nofeature-noha
+              FUNCTEST_SUITE_NAME=tempest_smoke_serial
+            block: true
+            same-node: true
+        - shell:
+            !include-raw-escape: ./apex-snapshot-create.sh
+        - shell:
+            !include-raw-escape: ./apex-upload-artifact.sh
+
+# FDIO promote
+- job-template:
+    name: 'apex-fdio-promote-daily-{stream}'
+
+    # Job template for promoting CSIT Snapshots
+    #
+    # Required Variables:
+    #     stream:    branch with - in place of / (eg. stable)
+    #     branch:    branch (eg. stable)
+    node: '{daily-slave}'
+
+    disabled: false
+
+    scm:
+        - git-scm
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - apex-parameter:
+            gs-pathname: '{gs-pathname}'
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            block-level: 'NODE'
+            blocking-jobs:
+                - 'apex-verify.*'
+                - 'apex-deploy.*'
+                - 'apex-build.*'
+                - 'apex-runner.*'
+                - 'apex-daily.*'
+
+    builders:
+        - 'apex-build'
+        - trigger-builds:
+          - project: 'apex-deploy-virtual-os-odl_l2-fdio-noha-{stream}'
+            predefined-parameters: |
+              BUILD_DIRECTORY=apex-fdio-promote-daily-{stream}
+              OPNFV_CLEAN=yes
+            git-revision: false
+            block: true
+            same-node: true
+        - shell:
+            !include-raw-escape: ./apex-snapshot-create.sh
+        - shell:
+            !include-raw-escape: ./apex-upload-artifact.sh
+
 - job-template:
     name: 'apex-gs-clean-{stream}'
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - apex-parameter:
             gs-pathname: '{gs-pathname}'
 
 - trigger:
     name: 'apex-master'
     triggers:
-        - timed: '0 0 20 8 *'
+        - timed: '0 3 1 1 7'
 - trigger:
-    name: 'apex-colorado'
+    name: 'apex-danube'
     triggers:
-        - timed: '0 3 * * *'
+        - timed: '0 12 * * *'
 - trigger:
     name: 'apex-gs-clean-{stream}'
     triggers:
index 2122959..38a729d 100644 (file)
         stream: master
         branch: '{stream}'
         gs-pathname: ''
-    colorado: &colorado
-        stream: colorado
+        disabled: false
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         gs-pathname: '/{stream}'
+        disabled: false
 #--------------------------------
 # POD, INSTALLER, AND BRANCH MAPPING
 #--------------------------------
 # CI POD's
 #--------------------------------
-#        colorado
+#        danube
 #--------------------------------
     pod:
         - armband-baremetal:
             slave-label: armband-baremetal
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - armband-virtual:
             slave-label: armband-virtual
             installer: fuel
-            <<: *colorado
+            <<: *danube
 #--------------------------------
 #        master
 #--------------------------------
 #--------------------------------
 # NONE-CI POD's
 #--------------------------------
-#        colorado
+#        danube
 #--------------------------------
         - arm-pod2:
             slave-label: arm-pod2
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - arm-pod3:
             slave-label: arm-pod3
             installer: fuel
-            <<: *colorado
+            <<: *danube
+        - arm-pod3-2:
+            slave-label: arm-pod3-2
+            installer: fuel
+            <<: *danube
 #--------------------------------
 #        master
 #--------------------------------
             slave-label: arm-pod3
             installer: fuel
             <<: *master
+        - arm-pod3-2:
+            slave-label: arm-pod3-2
+            installer: fuel
+            <<: *master
 #--------------------------------
 #       scenarios
 #--------------------------------
 - job-template:
     name: '{installer}-{scenario}-{pod}-daily-{stream}'
 
+    disabled: '{obj:disabled}'
+
     concurrent: false
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults':
             installer: '{installer}'
                 build-step-failure-threshold: 'never'
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
+        # 1.dovetail only master by now, not sync with A/B/C branches
+        # 2.here the stream means the SUT stream, dovetail stream is defined in its own job
+        # 3.only debug testsuite here(includes 3 basic testcase,
+        #   i.e. one tempest smoke ipv6, two vping from functest)
+        # 4.not used for release criteria or compliance,
+        #   only to debug the dovetail tool bugs with arm pods
+        - trigger-builds:
+            - project: 'dovetail-{installer}-{pod}-debug-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              block: true
+              same-node: true
+              block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
 
 - job-template:
     name: '{installer}-deploy-{pod}-daily-{stream}'
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults':
             installer: '{installer}'
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     wrappers:
         - build-name:
 - trigger:
     name: 'fuel-os-odl_l2-sfc-ha-armband-baremetal-master-trigger'
     triggers:
-        - timed: '0 0 * * 6'
+        - timed: '0 0,20 * * 6'
 - trigger:
     name: 'fuel-os-odl_l2-sfc-noha-armband-baremetal-master-trigger'
     triggers:
-        - timed: '0 0 * * 7'
+        - timed: '0 0,20 * * 7'
 
 #----------------------------------------------------------------------
-# Enea Armband CI Baremetal Triggers running against colorado branch
+# Enea Armband CI Baremetal Triggers running against danube branch
 #----------------------------------------------------------------------
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: '0 8 * * 1,3,5,7'
+        - timed: '0 4 * * 1,2,3,4,5'
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: '0 16 * * 2,7'
+        - timed: '0 8 * * 1,2,3,4,5'
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: '0 8 * * 2,4,6'
+        - timed: '0 12 * * 1,2,3,4,5'
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: '0 16 * * 1,4,6'
+        - timed: '0 16 * * 1,2,3,4,5'
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-noha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: '0 16 * * 3,5'
+        - timed: '0 20 * * 1,2,3,4,5'
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 4,8 * * 6,7'
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-armband-baremetal-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-armband-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 12,16 * * 6,7'
 #---------------------------------------------------------------
 # Enea Armband CI Virtual Triggers running against master branch
 #---------------------------------------------------------------
 - trigger:
     name: 'fuel-os-odl_l2-nofeature-ha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 1'
 - trigger:
     name: 'fuel-os-nosdn-nofeature-ha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 2'
 - trigger:
     name: 'fuel-os-odl_l3-nofeature-ha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 3'
 - trigger:
     name: 'fuel-os-odl_l2-bgpvpn-ha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 4'
 - trigger:
     name: 'fuel-os-odl_l2-nofeature-noha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 5'
 - trigger:
     name: 'fuel-os-odl_l2-sfc-ha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 6'
 - trigger:
     name: 'fuel-os-odl_l2-sfc-noha-armband-virtual-master-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 2 * * 7'
 #--------------------------------------------------------------------
-# Enea Armband CI Virtual Triggers running against colorado branch
+# Enea Armband CI Virtual Triggers running against danube branch
 #--------------------------------------------------------------------
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-noha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-armband-virtual-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-armband-virtual-danube-trigger'
     triggers:
         - timed: ''
 #----------------------------------------------------------
     triggers:
         - timed: ''
 #---------------------------------------------------------------
-# Enea Armband POD 2 Triggers running against colorado branch
+# Enea Armband POD 2 Triggers running against danube branch
 #---------------------------------------------------------------
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-noha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-arm-pod2-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-arm-pod2-danube-trigger'
     triggers:
         - timed: ''
 #----------------------------------------------------------
     triggers:
         - timed: ''
 #---------------------------------------------------------------
-# Enea Armband POD 3 Triggers running against colorado branch
+# Enea Armband POD 3 Triggers running against danube branch
 #---------------------------------------------------------------
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-nofeature-ha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l3-nofeature-ha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-bgpvpn-ha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-noha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-sfc-ha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-sfc-noha-arm-pod3-danube-trigger'
+    triggers:
+        - timed: ''
+#--------------------------------------------------------------------------
+# Enea Armband POD 3 Triggers running against master branch (aarch64 slave)
+#--------------------------------------------------------------------------
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-ha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-nofeature-ha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l3-nofeature-ha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-bgpvpn-ha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-noha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-sfc-ha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-sfc-noha-arm-pod3-2-master-trigger'
+    triggers:
+        - timed: ''
+#--------------------------------------------------------------------------
+# Enea Armband POD 3 Triggers running against danube branch (aarch64 slave)
+#--------------------------------------------------------------------------
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-ha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-noha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-arm-pod3-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-arm-pod3-2-danube-trigger'
     triggers:
         - timed: ''
index c8e58af..2e5aa39 100755 (executable)
@@ -8,7 +8,6 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-set -o errexit
 set -o nounset
 set -o pipefail
 
@@ -33,6 +32,14 @@ fi
 
 # set deployment parameters
 export TMPDIR=${WORKSPACE}/tmpdir
+
+# arm-pod3-2 is an aarch64 jenkins slave for the same POD as the
+# x86 jenkins slave arm-pod3; therefore we use the same pod name
+# to deploy the pod from both jenkins slaves
+if [[ "${NODE_NAME}" == "arm-pod3-2" ]]; then
+    NODE_NAME="arm-pod3"
+fi
+
 LAB_NAME=${NODE_NAME/-*}
 POD_NAME=${NODE_NAME/*-}
 
@@ -49,8 +56,8 @@ mkdir -p $TMPDIR
 
 cd $WORKSPACE
 if [[ $LAB_CONFIG_URL =~ ^(git|ssh):// ]]; then
-    echo "Cloning securedlab repo ${GIT_BRANCH##origin/}"
-    git clone --quiet --branch ${GIT_BRANCH##origin/} $LAB_CONFIG_URL lab-config
+    echo "Cloning securedlab repo $BRANCH"
+    git clone --quiet --branch $BRANCH $LAB_CONFIG_URL lab-config
     LAB_CONFIG_URL=file://${WORKSPACE}/lab-config
 
     # Source local_env if present, which contains POD-specific config
@@ -61,16 +68,33 @@ if [[ $LAB_CONFIG_URL =~ ^(git|ssh):// ]]; then
     fi
 fi
 
+if [[ "$NODE_NAME" =~ "virtual" ]]; then
+    POD_NAME="virtual_kvm"
+fi
+
 # releng wants us to use nothing else but opnfv.iso for now. We comply.
 ISO_FILE=$WORKSPACE/opnfv.iso
 
 # log file name
 FUEL_LOG_FILENAME="${JOB_NAME}_${BUILD_NUMBER}.log.tar.gz"
 
+# Deploy Cache (to enable just create the deploy-cache subdir)
+# NOTE: Only available when ISO files are cached using ISOSTORE mechanism
+DEPLOY_CACHE=${ISOSTORE:-/iso_mount/opnfv_ci}/${BRANCH##*/}/deploy-cache
+if [[ -d "${DEPLOY_CACHE}" ]]; then
+    echo "Deploy cache dir present."
+    echo "--------------------------------------------------------"
+    echo "Fuel@OPNFV deploy cache: ${DEPLOY_CACHE}"
+    DEPLOY_CACHE="-C ${DEPLOY_CACHE}"
+else
+    DEPLOY_CACHE=""
+fi
+
 # construct the command
 DEPLOY_COMMAND="$WORKSPACE/ci/deploy.sh -b ${LAB_CONFIG_URL} \
     -l $LAB_NAME -p $POD_NAME -s $DEPLOY_SCENARIO -i file://${ISO_FILE} \
-    -H -B ${DEFAULT_BRIDGE:-pxebr} -S $TMPDIR -L $WORKSPACE/$FUEL_LOG_FILENAME"
+    -H -B ${DEFAULT_BRIDGE:-pxebr} -S $TMPDIR -L $WORKSPACE/$FUEL_LOG_FILENAME \
+    ${DEPLOY_CACHE}"
 
 # log info to console
 echo "Deployment parameters"
index ed7897b..e2dd097 100755 (executable)
@@ -38,7 +38,7 @@ ISO_FILE=${WORKSPACE}/opnfv.iso
 # using ISOs for verify & merge jobs from local storage will be enabled later
 if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
     # check if we already have the ISO to avoid redownload
-    ISOSTORE=${ISOSTORE:-/iso_mount/opnfv_ci}/${GIT_BRANCH##*/}
+    ISOSTORE=${ISOSTORE:-/iso_mount/opnfv_ci}/${BRANCH##*/}
     if [[ -f "$ISOSTORE/$OPNFV_ARTIFACT" ]]; then
         echo "ISO exists locally. Skipping the download and using the file from ISO store"
         ln -s $ISOSTORE/$OPNFV_ARTIFACT ${ISO_FILE}
index 4b2a7b5..f6840a0 100644 (file)
         - master:
             branch: '{stream}'
             gs-pathname: ''
-        - colorado:
+            disabled: false
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
+            disabled: false
 
 - job-template:
     name: 'armband-{installer}-build-daily-{stream}'
 
+    disabled: '{obj:disabled}'
+
     concurrent: false
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 1
     parameters:
         - project-parameter:
             project: '{project}'
-        - 'opnfv-build-arm-defaults'
+            branch: '{branch}'
+        - 'opnfv-build-enea-defaults'
         - '{installer}-defaults'
         - armband-project-parameter:
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - pollscm:
index 90fdd7e..567456d 100644 (file)
@@ -12,7 +12,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
 #####################################
     phase:
         - 'basic':
-            slave-label: 'opnfv-build-arm'
+            slave-label: 'opnfv-build-enea'
         - 'build':
-            slave-label: 'opnfv-build-arm'
+            slave-label: 'opnfv-build-enea'
         - 'deploy-virtual':
-            slave-label: 'opnfv-build-arm'
+            slave-label: 'opnfv-build-enea'
         - 'smoke-test':
-            slave-label: 'opnfv-build-arm'
+            slave-label: 'opnfv-build-enea'
 #####################################
 # jobs
 #####################################
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             option: 'project'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -89,6 +86,7 @@
                     pattern: 'ci/**'
                   - compare-type: ANT
                     pattern: 'patches/**'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
@@ -97,9 +95,8 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
-        - 'opnfv-build-arm-defaults'
+        - 'opnfv-build-enea-defaults'
         - 'armband-verify-defaults':
             gs-pathname: '{gs-pathname}'
 
                 - name: 'armband-verify-basic-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'armband-verify-build-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'armband-verify-deploy-virtual-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'armband-verify-smoke-test-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 6
             block-level: 'NODE'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
         - '{installer}-defaults'
index a058ca1..a71cf11 100755 (executable)
@@ -96,6 +96,7 @@ ls -al $BUILD_DIRECTORY
     echo "OPNFV_GIT_URL=$(git config --get remote.origin.url)"
     echo "OPNFV_GIT_SHA1=$(git rev-parse HEAD)"
     echo "OPNFV_ARTIFACT_URL=$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.iso"
+    echo "OPNFV_ARTIFACT_SHA512SUM=$(sha512sum $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.iso | cut -d' ' -f1)"
     echo "OPNFV_BUILD_URL=$BUILD_URL"
 ) > $WORKSPACE/opnfv.properties
 
index 7059ac3..97987e2 100755 (executable)
@@ -28,7 +28,7 @@ if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
     # store ISO locally on NFS first
     ISOSTORE=${ISOSTORE:-/iso_mount/opnfv_ci}
     if [[ -d "$ISOSTORE" ]]; then
-        ISOSTORE=${ISOSTORE}/${GIT_BRANCH##*/}
+        ISOSTORE=${ISOSTORE}/${BRANCH##*/}
         mkdir -p $ISOSTORE
 
         # remove all but most recent 3 ISOs first to keep iso_mount clean & tidy
index c42efff..302bbc9 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: 'false'
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: 'false'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
similarity index 79%
rename from jjb/fastpathmetrics/fastpathmetrics.yml
rename to jjb/barometer/barometer.yml
index 40df385..9ec30e8 100644 (file)
@@ -3,45 +3,42 @@
 # They will only be enabled on request by projects!
 ###################################################
 - project:
-    name: fastpathmetrics
+    name: barometer
 
     project: '{name}'
 
     jobs:
-        - 'fastpathmetrics-verify-{stream}'
-        - 'fastpathmetrics-merge-{stream}'
-        - 'fastpathmetrics-daily-{stream}'
+        - 'barometer-verify-{stream}'
+        - 'barometer-merge-{stream}'
+        - 'barometer-daily-{stream}'
 
     stream:
         - master:
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
 
 - job-template:
-    name: 'fastpathmetrics-verify-{stream}'
+    name: 'barometer-verify-{stream}'
 
     disabled: '{obj:disabled}'
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -58,6 +55,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
@@ -70,7 +68,7 @@
             make
 
 - job-template:
-    name: 'fastpathmetrics-merge-{stream}'
+    name: 'barometer-merge-{stream}'
 
     project-type: freestyle
 
@@ -79,6 +77,7 @@
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 3
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
                 branches:
                     - branch-compare-type: 'ANT'
                       branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
         - shell: |
             pwd
             cd src
+            ./install_build_deps.sh
             make clobber
             make
 
 - job-template:
-    name: 'fastpathmetrics-daily-{stream}'
+    name: 'barometer-daily-{stream}'
 
     project-type: freestyle
 
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 3
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
          - timed: '@midnight'
         - shell: |
             pwd
             cd src
+            ./install_build_deps.sh
             make clobber
             make
index 7f2e6bf..c56ca19 100644 (file)
@@ -18,8 +18,8 @@
         gs-packagepath: '/{suite}'
         #docker tag used for version control
         docker-tag: 'latest'
-    colorado: &colorado
-        stream: colorado
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         gs-pathname: '/{stream}'
         gs-packagepath: '/{stream}/{suite}'
             slave-label: compass-baremetal
             installer: compass
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: compass-virtual
             installer: compass
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 
 #--------------------------------
 #        None-CI PODs
@@ -62,7 +62,7 @@
        #     slave-label: '{pod}'
        #     installer: joid
        #     auto-trigger-name: 'daily-trigger-disabled'
-       #     <<: *colorado
+       #     <<: *danube
        # - orange-pod2:
        #     slave-label: '{pod}'
        #     installer: joid
@@ -72,6 +72,8 @@
     suite:
         - 'rubbos'
         - 'vstf'
+        - 'posca_stress_traffic'
+        - 'posca_stress_ping'
 
     jobs:
         - 'bottlenecks-{installer}-{suite}-{pod}-daily-{stream}'
@@ -95,6 +97,7 @@
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{slave-label}-defaults'
         - '{installer}-defaults'
         - 'bottlenecks-params-{slave-label}'
             default: 'os-odl_l2-nofeature-ha'
         - string:
             name: GERRIT_REFSPEC_DEBUG
-            default: 'false'
+            default: 'true'
             description: "Gerrit refspec for debug."
         - string:
             name: SUITE_NAME
             description: "docker image tag used for version control"
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - 'bottlenecks-env-cleanup'
 - builder:
     name: bottlenecks-env-cleanup
     builders:
-        - shell: |
-            #!/bin/bash
-            set -e
-            [[ $GERRIT_REFSPEC_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
-
-            echo "Bottlenecks: docker containers/images cleaning up"
-            if [[ ! -z $(docker ps -a | grep opnfv/bottlenecks) ]]; then
-                echo "removing existing opnfv/bottlenecks containers"
-                docker ps -a | grep opnfv/bottlenecks | awk '{print $1}' | xargs docker rm -f >$redirect
-            fi
-
-            if [[ ! -z $(docker images | grep opnfv/bottlenecks) ]]; then
-                echo "Bottlenecks: docker images to remove:"
-                docker images | head -1 && docker images | grep opnfv/bottlenecks
-                image_tags=($(docker images | grep opnfv/bottlenecks | awk '{print $2}'))
-                for tag in "${image_tags[@]}"; do
-                    echo "Removing docker image opnfv/bottlenecks:$tag..."
-                    docker rmi opnfv/bottlenecks:$tag >$redirect
-                done
-            fi
+        - shell:
+            !include-raw: ./bottlenecks-cleanup.sh
 
 - builder:
     name: bottlenecks-run-suite
     builders:
-        - shell: |
-            #!/bin/bash
-            set -e
-            [[ $GERRIT_REFSPEC_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
-
-            echo "Bottlenecks: to pull image opnfv/bottlenecks:${DOCKER_TAG}"
-            docker pull opnfv/bottlenecks:$DOCKER_TAG >${redirect}
-
-            echo "Bottlenecks: docker start running"
-            opts="--privileged=true -id"
-            envs="-e INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP} \
-                  -e NODE_NAME=${NODE_NAME} -e EXTERNAL_NET=${EXTERNAL_NETWORK} \
-                  -e BOTTLENECKS_BRANCH=${BOTTLENECKS_BRANCH} -e GERRIT_REFSPEC_DEBUG=${GERRIT_REFSPEC_DEBUG} \
-                  -e BOTTLENECKS_DB_TARGET=${BOTTLENECKS_DB_TARGET} -e PACKAGE_URL=${PACKAGE_URL}"
-            cmd="sudo docker run ${opts} ${envs} opnfv/bottlenecks:${DOCKER_TAG} /bin/bash"
-            echo "Bottlenecks: docker cmd running ${cmd}"
-            ${cmd} >${redirect}
-
-            echo "Bottlenecks: obtain docker id"
-            container_id=$(docker ps | grep "opnfv/bottlenecks:${DOCKER_TAG}" | awk '{print $1}' | head -1)
-            if [ -z ${container_id} ]; then
-                echo "Cannot find opnfv/bottlenecks container ID ${container_id}. Please check if it exists."
-                docker ps -a
-                exit 1
-            fi
-
-            echo "Bottlenecks: to prepare openstack environment"
-            prepare_env="${REPO_DIR}/ci/prepare_env.sh"
-            echo "Bottlenecks: docker cmd running: ${prepare_env}"
-            sudo docker exec ${container_id} ${prepare_env}
-
-            echo "Bottlenecks: to run testsuite ${SUITE_NAME}"
-            run_testsuite="${REPO_DIR}/run_tests.sh -s ${SUITE_NAME}"
-            echo "Bottlenecks: docker cmd running: ${run_testsuite}"
-            sudo docker exec ${container_id} ${run_testsuite}
+        - shell:
+            !include-raw: ./bottlenecks-run-suite.sh
 
 ####################
 # parameter macros
diff --git a/jjb/bottlenecks/bottlenecks-cleanup.sh b/jjb/bottlenecks/bottlenecks-cleanup.sh
new file mode 100644 (file)
index 0000000..04e620c
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+#clean up correlated dockers and their images
+bash $WORKSPACE/docker/docker_cleanup.sh -d bottlenecks --debug
+bash $WORKSPACE/docker/docker_cleanup.sh -d yardstick --debug
+bash $WORKSPACE/docker/docker_cleanup.sh -d kibana --debug
+bash $WORKSPACE/docker/docker_cleanup.sh -d elasticsearch --debug
+bash $WORKSPACE/docker/docker_cleanup.sh -d influxdb --debug
index 523d363..5dced2a 100644 (file)
@@ -20,7 +20,7 @@
             #This is used for different test suite dependent packages storage
             gs-packagepath: '/{suite}'
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             gs-packagepath: '/{stream}/{suite}'
@@ -29,6 +29,8 @@
     suite:
         - 'rubbos'
         - 'vstf'
+        - 'posca_stress_traffic'
+        - 'posca_stress_ping'
 
 ################################
 # job templates
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -71,8 +70,8 @@
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
     builders:
-        - bottlenecks-hello
-        #- bottlenecks-unit-tests
+        #- bottlenecks-hello
+        - bottlenecks-unit-tests
 
 - job-template:
     name: 'bottlenecks-merge-{stream}'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - bottlenecks-parameter:
             gs-packagepath: '{gs-packagepath}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - 'bottlenecks-builder-upload-artifact'
             # install python packages
             easy_install -U setuptools
             easy_install -U pip
-            pip install -r requirements.txt
+            pip install -r $WORKSPACE/requirements/verify.txt
 
             # unit tests
-            /bin/bash $WORKSPACE/tests.sh
+            /bin/bash $WORKSPACE/verify.sh
 
             deactivate
 
             #!/bin/bash
             set -o errexit
 
-            echo "hello"
+            echo -e "Wellcome to Bottlenecks! \nMerge event is planning to support more functions! "
diff --git a/jjb/bottlenecks/bottlenecks-run-suite.sh b/jjb/bottlenecks/bottlenecks-run-suite.sh
new file mode 100644 (file)
index 0000000..0df659a
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/bash
+#set -e
+[[ $GERRIT_REFSPEC_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
+BOTTLENECKS_IMAGE=opnfv/bottlenecks
+
+if [[ $SUITE_NAME == rubbos || $SUITE_NAME == vstf ]]; then
+    echo "Bottlenecks: to pull image $BOTTLENECKS_IMAGE:${DOCKER_TAG}"
+    docker pull $BOTTLENECKS_IMAGE:$DOCKER_TAG >${redirect}
+
+    echo "Bottlenecks: docker start running"
+    opts="--privileged=true -id"
+    envs="-e INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP} \
+          -e NODE_NAME=${NODE_NAME} -e EXTERNAL_NET=${EXTERNAL_NETWORK} \
+          -e BOTTLENECKS_BRANCH=${BOTTLENECKS_BRANCH} -e GERRIT_REFSPEC_DEBUG=${GERRIT_REFSPEC_DEBUG} \
+          -e BOTTLENECKS_DB_TARGET=${BOTTLENECKS_DB_TARGET} -e PACKAGE_URL=${PACKAGE_URL}"
+    cmd="sudo docker run ${opts} ${envs} $BOTTLENECKS_IMAGE:${DOCKER_TAG} /bin/bash"
+    echo "Bottlenecks: docker cmd running ${cmd}"
+    ${cmd} >${redirect}
+
+    echo "Bottlenecks: obtain docker id"
+    container_id=$(docker ps | grep "$BOTTLENECKS_IMAGE:${DOCKER_TAG}" | awk '{print $1}' | head -1)
+    if [ -z ${container_id} ]; then
+        echo "Cannot find $BOTTLENECKS_IMAGE container ID ${container_id}. Please check if it exists."
+        docker ps -a
+        exit 1
+    fi
+
+    echo "Bottlenecks: to prepare openstack environment"
+    prepare_env="${REPO_DIR}/ci/prepare_env.sh"
+    echo "Bottlenecks: docker cmd running: ${prepare_env}"
+    sudo docker exec ${container_id} ${prepare_env}
+
+    echo "Bottlenecks: to run testsuite ${SUITE_NAME}"
+    run_testsuite="${REPO_DIR}/run_tests.sh -s ${SUITE_NAME}"
+    echo "Bottlenecks: docker cmd running: ${run_testsuite}"
+    sudo docker exec ${container_id} ${run_testsuite}
+else
+    echo "Bottlenecks: installing POSCA docker-compose"
+    if [ -d usr/local/bin/docker-compose ]; then
+        rm -rf usr/local/bin/docker-compose
+    fi
+    curl -L https://github.com/docker/compose/releases/download/1.11.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
+    chmod +x /usr/local/bin/docker-compose
+
+    echo "Bottlenecks: composing up dockers"
+    cd $WORKSPACE
+    docker-compose -f $WORKSPACE/docker/bottleneck-compose/docker-compose.yml up -d
+
+    echo "Bottlenecks: running traffic stress/factor testing in posca testsuite "
+    POSCA_SCRIPT=/home/opnfv/bottlenecks/testsuites/posca
+    if [[ $SUITE_NAME == posca_stress_traffic ]]; then
+        TEST_CASE=posca_factor_system_bandwidth
+        echo "Bottlenecks: pulling tutum/influxdb for yardstick"
+        docker pull tutum/influxdb:0.13
+        sleep 5
+        docker exec bottleneckcompose_bottlenecks_1 python ${POSCA_SCRIPT}/run_posca.py testcase $TEST_CASE
+    elif [[ $SUITE_NAME == posca_stress_ping ]]; then
+        TEST_CASE=posca_factor_ping
+        sleep 5
+        docker exec bottleneckcompose_bottlenecks_1 python ${POSCA_SCRIPT}/run_posca.py testcase $TEST_CASE
+    fi
+
+    echo "Bottlenecks: cleaning up docker-compose images and dockers"
+    docker-compose -f $WORKSPACE/docker/bottleneck-compose/docker-compose.yml down --rmi all
+fi
\ No newline at end of file
index eb91131..237f894 100644 (file)
         stream: master
         branch: '{stream}'
         gs-pathname: ''
-    colorado: &colorado
-        stream: colorado
+        disabled: false
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         gs-pathname: '/{stream}'
+        disabled: false
 #--------------------------------
 # POD, INSTALLER, AND BRANCH MAPPING
 #--------------------------------
     pod:
         - baremetal:
             slave-label: compass-baremetal
-            os-version: 'trusty'
+            os-version: 'xenial'
             <<: *master
         - virtual:
             slave-label: compass-virtual
-            os-version: 'trusty'
+            os-version: 'xenial'
             <<: *master
         - baremetal:
             slave-label: compass-baremetal
-            os-version: 'trusty'
-            <<: *colorado
+            os-version: 'xenial'
+            <<: *danube
         - virtual:
             slave-label: compass-virtual
-            os-version: 'trusty'
-            <<: *colorado
+            os-version: 'xenial'
+            <<: *danube
 #--------------------------------
 #        master
 #--------------------------------
-        - huawei-pod5:
-            slave-label: '{pod}'
+        - baremetal-centos:
+            slave-label: 'intel-pod8'
             os-version: 'centos7'
             <<: *master
 
         - 'os-nosdn-nofeature-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
         - 'os-odl_l2-nofeature-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
         - 'os-odl_l3-nofeature-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
         - 'os-onos-nofeature-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
         - 'os-ocl-nofeature-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
         - 'os-onos-sfc-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
         - 'os-odl_l2-moon-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: 'xenial'
         - 'os-nosdn-kvm-ha':
             disabled: false
             auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
-            openstack-os-version: ''
+        - 'os-nosdn-openo-ha':
+            disabled: false
+            auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger'
+
 
     jobs:
         - 'compass-{scenario}-{pod}-daily-{stream}'
 - job-template:
     name: 'compass-{scenario}-{pod}-daily-{stream}'
 
+    disabled: '{obj:disabled}'
+
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - compass-ci-parameter:
             installer: '{installer}'
             gs-pathname: '{gs-pathname}'
               predefined-parameters: |
                 DEPLOY_SCENARIO={scenario}
                 COMPASS_OS_VERSION={os-version}
-                COMPASS_OS_VERSION_OPTION={openstack-os-version}
               same-node: true
               block: true
         - trigger-builds:
         #dovetail only master by now, not sync with A/B/C branches
         #here the stream means the SUT stream, dovetail stream is defined in its own job
         - trigger-builds:
-            - project: 'dovetail-compass-{pod}-basic-{stream}'
+            - project: 'dovetail-compass-{pod}-debug-{stream}'
               current-parameters: false
               predefined-parameters:
                 DEPLOY_SCENARIO={scenario}
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - compass-ci-parameter:
             installer: '{installer}'
             gs-pathname: '{gs-pathname}'
         - '{installer}-defaults'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     wrappers:
         - build-name:
         - shell:
             !include-raw-escape: ./compass-deploy.sh
 
-    publishers:
-        - archive:
-            artifacts: 'ansible.log'
-            allow-empty: 'true'
-            fingerprint: true
-
 ########################
 # parameter macros
 ########################
         - choice:
             name: COMPASS_OPENSTACK_VERSION
             choices:
-                - 'mitaka'
-                - 'liberty'
-        - choice:
-            name: COMPASS_OS_VERSION_OPTION
-            choices:
-                - ''
-                - 'xenial'
+                - 'newton'
 
 ########################
 # trigger macros
 ########################
 - trigger:
-    name: 'compass-os-nosdn-nofeature-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-nosdn-nofeature-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: '0 19 * * *'
 - trigger:
-    name: 'compass-os-odl_l2-nofeature-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-nosdn-openo-ha-baremetal-centos-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'compass-os-odl_l2-nofeature-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: '0 23 * * *'
 - trigger:
-    name: 'compass-os-odl_l3-nofeature-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-odl_l3-nofeature-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: '0 15 * * *'
 - trigger:
-    name: 'compass-os-onos-nofeature-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-onos-nofeature-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: '0 7 * * *'
 - trigger:
-    name: 'compass-os-ocl-nofeature-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-ocl-nofeature-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: '0 11 * * *'
 - trigger:
-    name: 'compass-os-onos-sfc-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-onos-sfc-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: '0 3 * * *'
 - trigger:
-    name: 'compass-os-odl_l2-moon-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-odl_l2-moon-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'compass-os-nosdn-kvm-ha-huawei-pod5-master-trigger'
+    name: 'compass-os-nosdn-kvm-ha-baremetal-centos-master-trigger'
     triggers:
         - timed: ''
 
     name: 'compass-os-nosdn-nofeature-ha-baremetal-master-trigger'
     triggers:
         - timed: '0 2 * * *'
+- trigger:
+    name: 'compass-os-nosdn-openo-ha-baremetal-master-trigger'
+    triggers:
+        - timed: '0 3 * * *'
 - trigger:
     name: 'compass-os-odl_l2-nofeature-ha-baremetal-master-trigger'
     triggers:
         - timed: ''
 
 - trigger:
-    name: 'compass-os-nosdn-nofeature-ha-baremetal-colorado-trigger'
+    name: 'compass-os-nosdn-nofeature-ha-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 9 * * *'
 - trigger:
-    name: 'compass-os-odl_l2-nofeature-ha-baremetal-colorado-trigger'
+    name: 'compass-os-nosdn-openo-ha-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 13 * * *'
 - trigger:
-    name: 'compass-os-odl_l3-nofeature-ha-baremetal-colorado-trigger'
+    name: 'compass-os-odl_l2-nofeature-ha-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 17 * * *'
 - trigger:
-    name: 'compass-os-onos-nofeature-ha-baremetal-colorado-trigger'
+    name: 'compass-os-odl_l3-nofeature-ha-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 21 * * *'
 - trigger:
-    name: 'compass-os-ocl-nofeature-ha-baremetal-colorado-trigger'
+    name: 'compass-os-onos-nofeature-ha-baremetal-danube-trigger'
     triggers:
-        - timed: ''
+        - timed: '0 1 * * *'
 - trigger:
-    name: 'compass-os-onos-sfc-ha-baremetal-colorado-trigger'
+    name: 'compass-os-ocl-nofeature-ha-baremetal-danube-trigger'
+    triggers:
+        - timed: '0 5 * * *'
+- trigger:
+    name: 'compass-os-onos-sfc-ha-baremetal-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'compass-os-odl_l2-moon-ha-baremetal-colorado-trigger'
+    name: 'compass-os-odl_l2-moon-ha-baremetal-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'compass-os-nosdn-kvm-ha-baremetal-colorado-trigger'
+    name: 'compass-os-nosdn-kvm-ha-baremetal-danube-trigger'
     triggers:
         - timed: ''
 
     name: 'compass-os-nosdn-nofeature-ha-virtual-master-trigger'
     triggers:
         - timed: '0 21 * * *'
+- trigger:
+    name: 'compass-os-nosdn-openo-ha-virtual-master-trigger'
+    triggers:
+        - timed: '0 22 * * *'
 - trigger:
     name: 'compass-os-odl_l2-nofeature-ha-virtual-master-trigger'
     triggers:
         - timed: ''
 
 - trigger:
-    name: 'compass-os-nosdn-nofeature-ha-virtual-colorado-trigger'
+    name: 'compass-os-nosdn-nofeature-ha-virtual-danube-trigger'
     triggers:
         - timed: '0 21 * * *'
 - trigger:
-    name: 'compass-os-odl_l2-nofeature-ha-virtual-colorado-trigger'
+    name: 'compass-os-nosdn-openo-ha-virtual-danube-trigger'
+    triggers:
+        - timed: '0 22 * * *'
+- trigger:
+    name: 'compass-os-odl_l2-nofeature-ha-virtual-danube-trigger'
     triggers:
         - timed: '0 20 * * *'
 - trigger:
-    name: 'compass-os-odl_l3-nofeature-ha-virtual-colorado-trigger'
+    name: 'compass-os-odl_l3-nofeature-ha-virtual-danube-trigger'
     triggers:
         - timed: '0 19 * * *'
 - trigger:
-    name: 'compass-os-onos-nofeature-ha-virtual-colorado-trigger'
+    name: 'compass-os-onos-nofeature-ha-virtual-danube-trigger'
     triggers:
         - timed: '0 18 * * *'
 - trigger:
-    name: 'compass-os-ocl-nofeature-ha-virtual-colorado-trigger'
+    name: 'compass-os-ocl-nofeature-ha-virtual-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'compass-os-onos-sfc-ha-virtual-colorado-trigger'
+    name: 'compass-os-onos-sfc-ha-virtual-danube-trigger'
     triggers:
         - timed: '0 15 * * *'
 - trigger:
-    name: 'compass-os-odl_l2-moon-ha-virtual-colorado-trigger'
+    name: 'compass-os-odl_l2-moon-ha-virtual-danube-trigger'
     triggers:
         - timed: '0 14 * * *'
 - trigger:
-    name: 'compass-os-nosdn-kvm-ha-virtual-colorado-trigger'
+    name: 'compass-os-nosdn-kvm-ha-virtual-danube-trigger'
     triggers:
         - timed: ''
index 65e44b6..534e17e 100644 (file)
@@ -29,20 +29,21 @@ cd $WORKSPACE
 
 export OS_VERSION=${COMPASS_OS_VERSION}
 export OPENSTACK_VERSION=${COMPASS_OPENSTACK_VERSION}
-if [[ "${COMPASS_OS_VERSION_OPTION}" = "xenial" ]] && [[ "${OPENSTACK_VERSION}" = "mitaka" ]]; then
-    export OPENSTACK_VERSION=${OPENSTACK_VERSION}_${COMPASS_OS_VERSION_OPTION}
-    export OS_VERSION=${COMPASS_OS_VERSION_OPTION}
-fi
 
 if [[ "${DEPLOY_SCENARIO}" =~ "-ocl" ]]; then
     export NETWORK_CONF_FILE=network_ocl.yml
-    export OPENSTACK_VERSION=liberty
 elif [[ "${DEPLOY_SCENARIO}" =~ "-onos" ]]; then
     export NETWORK_CONF_FILE=network_onos.yml
+elif [[ "${DEPLOY_SCENARIO}" =~ "-openo" ]]; then
+    export NETWORK_CONF_FILE=network_openo.yml
 else
     export NETWORK_CONF_FILE=network.yml
 fi
 
+if [[ "$NODE_NAME" =~ "intel-pod8" ]]; then
+    export OS_MGMT_NIC=em4
+fi
+
 if [[ "$NODE_NAME" =~ "-virtual" ]]; then
     export NETWORK_CONF=$CONFDIR/vm_environment/$NODE_NAME/${NETWORK_CONF_FILE}
     export DHA_CONF=$CONFDIR/vm_environment/${DEPLOY_SCENARIO}.yml
@@ -52,7 +53,11 @@ else
     export DHA_CONF=$CONFDIR/hardware_environment/$NODE_NAME/${DEPLOY_SCENARIO}.yml
 fi
 
-./deploy.sh --dha ${DHA_CONF} --network ${NETWORK_CONF}
+export DHA=${DHA_CONF}
+export NETWORK=${NETWORK_CONF}
+
+source ./ci/deploy_ci.sh
+
 if [ $? -ne 0 ]; then
     echo "depolyment failed!"
     deploy_ret=1
@@ -62,7 +67,4 @@ echo
 echo "--------------------------------------------------------"
 echo "Done!"
 
-ssh_options="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
-sshpass -p root scp 2>/dev/null $ssh_options root@${INSTALLER_IP}:/var/ansible/run/openstack_${OPENSTACK_VERSION}-opnfv2/ansible.log ./  &> /dev/null
-
 exit $deploy_ret
diff --git a/jjb/compass4nfv/compass-dovetail-jobs.yml b/jjb/compass4nfv/compass-dovetail-jobs.yml
new file mode 100644 (file)
index 0000000..30c80e6
--- /dev/null
@@ -0,0 +1,197 @@
+- project:
+
+    name: 'compass-dovetail-jobs'
+    installer: 'compass'
+    project: 'compass4nfv'
+#----------------------------------
+# BRANCH ANCHORS
+#----------------------------------
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        gs-pathname: '/{stream}'
+        disabled: false
+        dovetail-branch: master
+#------------------------------------
+# POD, INSTALLER, AND BRANCH MAPPING
+#------------------------------------
+#        CI PODs
+#------------------------------------
+    pod:
+        - baremetal:
+            slave-label: compass-baremetal
+            os-version: 'xenial'
+            <<: *danube
+#-----------------------------------
+# scenarios
+#-----------------------------------
+    scenario:
+        - 'os-nosdn-nofeature-ha':
+            disabled: true
+            auto-trigger-name: 'compass-{scenario}-{pod}-weekly-{stream}-trigger'
+
+    jobs:
+        - 'compass-{scenario}-{pod}-weekly-{stream}'
+        - 'compass-deploy-{pod}-weekly-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: 'compass-{scenario}-{pod}-weekly-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'compass-os-.*?-{pod}-daily-.*?'
+                - 'compass-os-.*?-{pod}-weekly-.*?'
+            block-level: 'NODE'
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+
+    triggers:
+        - '{auto-trigger-name}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - compass-dovetail-parameter:
+            installer: '{installer}'
+            gs-pathname: '{gs-pathname}'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: '{scenario}'
+        - '{slave-label}-defaults'
+        - '{installer}-defaults'
+
+    triggers:
+        - '{auto-trigger-name}'
+
+    builders:
+        - description-setter:
+            description: "POD: $NODE_NAME"
+        - trigger-builds:
+            - project: 'compass-deploy-{pod}-weekly-{stream}'
+              current-parameters: false
+              predefined-parameters: |
+                DEPLOY_SCENARIO={scenario}
+                COMPASS_OS_VERSION={os-version}
+              same-node: true
+              block: true
+        - trigger-builds:
+            - project: 'dovetail-compass-{pod}-compliance_set-weekly-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              block: true
+              same-node: true
+              block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+        - trigger-builds:
+            - project: 'dovetail-compass-{pod}-debug-weekly-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              block: true
+              same-node: true
+              block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+
+- job-template:
+    name: 'compass-deploy-{pod}-weekly-{stream}'
+
+    disabled: false
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'compass-deploy-{pod}-daily-.*?'
+                - 'compass-deploy-{pod}-weekly-.*'
+                - 'compass-verify-deploy-.*?'
+            block-level: 'NODE'
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+        - timeout:
+            timeout: 120
+            abort: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - compass-dovetail-parameter:
+            installer: '{installer}'
+            gs-pathname: '{gs-pathname}'
+        - '{slave-label}-defaults'
+        - '{installer}-defaults'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+
+
+    builders:
+        - description-setter:
+            description: "POD: $NODE_NAME"
+        - shell:
+            !include-raw-escape: ./compass-download-artifact.sh
+        - shell:
+            !include-raw-escape: ./compass-deploy.sh
+
+########################
+# parameter macros
+########################
+- parameter:
+    name: compass-dovetail-parameter
+    parameters:
+        - string:
+            name: BUILD_DIRECTORY
+            default: $WORKSPACE/build_output
+            description: "Directory where the build artifact will be located upon the completion of the build."
+        - string:
+            name: GS_URL
+            default: '$GS_BASE{gs-pathname}'
+            description: "URL to Google Storage."
+        - choice:
+            name: COMPASS_OPENSTACK_VERSION
+            choices:
+                - 'newton'
+
+########################
+# trigger macros
+########################
+- trigger:
+    name: 'compass-os-nosdn-nofeature-ha-baremetal-weekly-danube-trigger'
+    triggers:
+        - timed: 'H H * * 0'
+
+- trigger:
+    name: 'dovetail-weekly-trigger'
+    triggers:
+        - timed: 'H H * * 0'
index 3a52e91..5948245 100644 (file)
         - master:
             branch: '{stream}'
             gs-pathname: ''
-        - colorado:
+            ppa-pathname: '/{stream}'
+            disabled: false
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
+            ppa-pathname: '/{stream}'
+            disabled: false
 
     jobs:
         - 'compass-build-iso-{stream}'
 - job-template:
     name: 'compass-build-iso-{stream}'
 
+    disabled: '{obj:disabled}'
+
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - compass-project-parameter:
             installer: '{installer}'
             gs-pathname: '{gs-pathname}'
+            ppa-pathname: '{ppa-pathname}'
         - 'opnfv-build-ubuntu-defaults'
         - '{installer}-defaults'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - timed: 'H 8 * * *'
 
     description: "build ppa(using docker) in huawei lab"
 
+    disabled: '{obj:disabled}'
+
     node: huawei-build
 
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - compass-project-parameter:
             installer: '{installer}'
             gs-pathname: '{gs-pathname}'
+            ppa-pathname: '{ppa-pathname}'
         - '{node}-defaults'
         - '{installer}-defaults'
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - shell:
             description: "URL to Google Storage."
         - string:
             name: PPA_REPO
-            default: "http://205.177.226.237:9999{gs-pathname}"
+            default: "http://artifacts.opnfv.org/compass4nfv/package{ppa-pathname}"
         - string:
             name: PPA_CACHE
             default: "$WORKSPACE/work/repo/"
-        - choice:
-            name: COMPASS_OPENSTACK_VERSION
-            choices:
-                - 'mitaka'
-                - 'liberty'
-        - choice:
-            name: COMPASS_OS_VERSION
-            choices:
-                - 'trusty'
-                - 'centos7'
 
index 290da36..56f54d8 100644 (file)
         - master:
             branch: '{stream}'
             gs-pathname: ''
+            ppa-pathname: '/{stream}'
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
-            disabled: true
+            ppa-pathname: '/{stream}'
+            disabled: false
 
     distro:
-        - 'trusty':
+        - 'xenial':
             disabled: false
-            os-version: 'trusty'
+            os-version: 'xenial'
             openstack-os-version: ''
         - 'centos7':
             disabled: false
@@ -37,6 +39,7 @@
 #####################################
     jobs:
         - 'compass-verify-{distro}-{stream}'
+        - 'compass-verify-k8-{distro}-{stream}'
         - 'compass-verify-{phase}-{distro}-{stream}'
 #####################################
 # job templates
@@ -51,6 +54,7 @@
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             block-level: 'NODE'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 120
             fail: true
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -98,6 +98,7 @@
                 file-paths:
                   - compare-type: ANT
                     pattern: '**/*'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'compass-virtual-defaults'
+        - '{installer}-defaults'
         - 'compass-verify-defaults':
             installer: '{installer}'
             gs-pathname: '{gs-pathname}'
+            ppa-pathname: '{ppa-pathname}'
         - string:
             name: DEPLOY_SCENARIO
             default: 'os-nosdn-nofeature-ha'
             name: basic
             condition: SUCCESSFUL
             projects:
-                - name: 'compass-verify-basic-{stream}'
+                - name: 'opnfv-lint-verify-{stream}'
+                  current-parameters: true
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+                - name: 'opnfv-yamllint-verify-{stream}'
                   current-parameters: true
                   node-parameters: true
                   kill-phase-on: FAILURE
                   current-parameters: true
                   predefined-parameters: |
                     COMPASS_OS_VERSION={os-version}
-                    COMPASS_OS_VERSION_OPTION={openstack-os-version}
                   node-parameters: true
                   kill-phase-on: FAILURE
                   abort-all-job: true
                   node-parameters: true
                   kill-phase-on: NEVER
                   abort-all-job: true
+                - name: 'functest-compass-virtual-suite-{stream}'
+                  current-parameters: true
+                  predefined-parameters:
+                    FUNCTEST_SUITE_NAME=vping_ssh
+                  node-parameters: true
+                  kill-phase-on: NEVER
+                  abort-all-job: true
+
+- job-template:
+    name: 'compass-verify-k8-{distro}-{stream}'
+
+    project-type: multijob
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'compass-verify-[^-]*-[^-]*'
+                - 'compass-os-.*?-virtual-daily-.*?'
+            block-level: 'NODE'
+
+    scm:
+        - git-scm-gerrit
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 120
+            fail: true
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - comment-added-contains-event:
+                    comment-contains-value: 'check k8'
+                - comment-added-contains-event:
+                    comment-contains-value: 'verify k8'
+                - comment-added-contains-event:
+                    comment-contains-value: 'check kubernetes'
+                - comment-added-contains-event:
+                    comment-contains-value: 'verify kubernetes'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: '**/*'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**'
+            readable-message: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'compass-virtual-defaults'
+        - '{installer}-defaults'
+        - 'compass-verify-defaults':
+            installer: '{installer}'
+            gs-pathname: '{gs-pathname}'
+            ppa-pathname: '{ppa-pathname}'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'k8-nosdn-nofeature-ha'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: basic
+            condition: SUCCESSFUL
+            projects:
+                - name: 'opnfv-lint-verify-{stream}'
+                  current-parameters: true
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+                - name: 'opnfv-yamllint-verify-{stream}'
+                  current-parameters: true
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: deploy-virtual
+            condition: SUCCESSFUL
+            projects:
+                - name: 'compass-verify-deploy-virtual-{distro}-{stream}'
+                  current-parameters: true
+                  predefined-parameters: |
+                    COMPASS_OS_VERSION={os-version}
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
 
 - job-template:
     name: 'compass-verify-{phase}-{distro}-{stream}'
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
             block-level: 'NODE'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 120
             fail: true
             description: "Built on $NODE_NAME"
         - '{project}-verify-{phase}-macro'
 
-    publishers:
-        - archive:
-            artifacts: 'ansible.log'
-            allow-empty: 'true'
-            fingerprint: true
 #####################################
 # builder macros
 #####################################
             description: "URL to Google Storage."
         - string:
             name: PPA_REPO
-            default: "http://205.177.226.237:9999{gs-pathname}"
+            default: "http://artifacts.opnfv.org/compass4nfv/package{ppa-pathname}"
         - string:
             name: PPA_CACHE
             default: "$WORKSPACE/work/repo/"
         - choice:
             name: COMPASS_OPENSTACK_VERSION
             choices:
-                - 'mitaka'
-                - 'liberty'
+                - 'newton'
         - choice:
             name: COMPASS_OS_VERSION
             choices:
-                - 'trusty'
+                - 'xenial'
                 - 'centos7'
index a5f556a..d2ce649 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index b504578..d06afe4 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
 
     builders:
         - shell: |
-            echo "Nothing to verify!"
+            #!/bin/bash
+            set -o errexit
+            set -o nounset
+            set -o pipefail
+
+           # shellcheck -f tty tests/*.sh
index d6c8601..dc209d6 100644 (file)
         branch: '{stream}'
         gs-pathname: ''
         docker-tag: 'latest'
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        gs-pathname: '/{stream}'
+        docker-tag: 'stable'
 
 #--------------------------------
 # POD, INSTALLER, AND BRANCH MAPPING
@@ -24,7 +29,9 @@
         - intel-pod2:
             installer: apex
             <<: *master
-
+        - intel-pod2:
+            installer: apex
+            <<: *danube
 #--------------------------------
 
     testsuite:
@@ -42,6 +49,7 @@
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
@@ -57,6 +65,7 @@
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{pod}-defaults'
         - '{installer}-defaults'
         - cperf-parameter:
             docker-tag: '{docker-tag}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - 'cperf-{testsuite}-builder'
     builders:
         - shell: |
             #!/bin/bash
-            set +e
-            # TODO: need to figure out the logic to get ${CONTROLLER_IP} used below
+            set -o errexit
+            set -o nounset
+            set -o pipefail
+            undercloud_mac=$(sudo virsh domiflist undercloud | grep default | \
+                              grep -Eo "[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+")
+            INSTALLER_IP=$(/usr/sbin/arp -e | grep ${undercloud_mac} | awk {'print $1'})
+
+            sudo scp -o StrictHostKeyChecking=no root@$INSTALLER_IP:/home/stack/overcloudrc /tmp/overcloudrc
+            sudo chmod 755 /tmp/overcloudrc
+            source /tmp/overcloudrc
+
+            # robot suites need the ssh key to log in to controller nodes, so throwing it
+            # in tmp, and mounting /tmp as $HOME as far as robot is concerned
+            sudo rm -rf /tmp/.ssh
+            sudo mkdir /tmp/.ssh
+            sudo chmod 0700 /tmp/.ssh
+            sudo scp -o StrictHostKeyChecking=no root@$INSTALLER_IP:/home/stack/.ssh/id_rsa /tmp/.ssh/
+            sudo chown -R jenkins-ci:jenkins-ci /tmp/.ssh
+            # done with sudo. jenkins-ci is the user from this point
+            chmod 0600 /tmp/.ssh/id_rsa
+
+            # cbench requires the openflow drop test feature to be installed.
+            sshpass -p karaf ssh -o StrictHostKeyChecking=no \
+                                 -o UserKnownHostsFile=/dev/null \
+                                 -o LogLevel=error \
+                                 -p 8101 karaf@$SDN_CONTROLLER_IP \
+                                  feature:install odl-openflowplugin-flow-services-ui odl-openflowplugin-drop-test
+
             docker pull opnfv/cperf:$DOCKER_TAG
-            robot_cmd="pybot -e exclude -v ODL_SYSTEM_IP:${CONTROLLER_IP} -v switch_count:100 -v loops:10 \
-                              -v TOOLS_SYSTEM_IP:localhost -v duration_in_seconds:60"
+
+            robot_cmd="pybot -e exclude -L TRACE -d /tmp \
+                        -v ODL_SYSTEM_1_IP:${SDN_CONTROLLER_IP} \
+                        -v ODL_SYSTEM_IP:${SDN_CONTROLLER_IP} \
+                        -v BUNDLEFOLDER:/opt/opendaylight \
+                        -v RESTCONFPORT:8081 \
+                        -v USER_HOME:/tmp \
+                        -v USER:heat-admin \
+                        -v ODL_SYSTEM_USER:heat-admin \
+                        -v TOOLS_SYSTEM_IP:localhost \
+                        -v of_port:6653"
             robot_suite="/home/opnfv/repos/odl_test/csit/suites/openflowplugin/Performance/010_Cbench.robot"
-            docker run opnfv/cperf:$DOCKER_TAG ${robot_cmd} ${robot_suite}
+
+            docker run -i -v /tmp:/tmp opnfv/cperf:$DOCKER_TAG ${robot_cmd} ${robot_suite}
 
 - builder:
     name: cperf-cleanup
diff --git a/jjb/daisy4nfv/daisy-daily-jobs.yml b/jjb/daisy4nfv/daisy-daily-jobs.yml
new file mode 100644 (file)
index 0000000..aac76ba
--- /dev/null
@@ -0,0 +1,199 @@
+# jenkins job templates for Daisy
+# TODO
+# [ ] enable baremetal jobs after baremetal deployment finish
+# [ ] enable jobs in danuble
+# [ ] add more scenarios
+# [ ] integration with yardstick
+
+- project:
+
+    name: 'daisy'
+    project: '{name}'
+    installer: '{name}'
+
+#--------------------------------
+# BRANCH ANCHORS
+#--------------------------------
+    master: &master
+        stream: master
+        branch: '{stream}'
+        disabled: false
+        gs-pathname: ''
+#--------------------------------
+# POD, INSTALLER, AND BRANCH MAPPING
+#--------------------------------
+#        CI PODs
+#--------------------------------
+    pod:
+        - baremetal:
+            slave-label: daisy-baremetal
+            <<: *master
+        - virtual:
+            slave-label: daisy-virtual
+            <<: *master
+#--------------------------------
+#        None-CI PODs
+#--------------------------------
+
+#--------------------------------
+#       scenarios
+#--------------------------------
+    scenario:
+        # HA scenarios
+        - 'os-nosdn-nofeature-ha':
+            auto-trigger-name: 'daisy-{scenario}-{pod}-daily-{stream}-trigger'
+        # NOHA scenarios
+        - 'os-nosdn-nofeature-noha':
+            auto-trigger-name: 'daisy-{scenario}-{pod}-daily-{stream}-trigger'
+
+    jobs:
+        - '{project}-{scenario}-{pod}-daily-{stream}'
+        - '{project}-deploy-{pod}-daily-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: '{project}-{scenario}-{pod}-daily-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'daisy-daily-.*'
+            block-level: 'NODE'
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+
+    triggers:
+        - '{auto-trigger-name}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{installer}-defaults'
+        - '{slave-label}-defaults':
+            installer: '{installer}'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: '{scenario}'
+        - 'daisy-project-parameter':
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+            description: "POD: $NODE_NAME"
+        - trigger-builds:
+            - project: 'daisy-deploy-{pod}-daily-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              same-node: true
+              block: true
+        - trigger-builds:
+            - project: 'functest-daisy-{pod}-daily-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              same-node: true
+              block: true
+              block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+
+- job-template:
+    name: '{project}-deploy-{pod}-daily-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'daisy.*-deploy-({pod})?-daily-.*'
+            block-level: 'NODE'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{installer}-defaults'
+        - '{slave-label}-defaults':
+            installer: '{installer}'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-nosdn-nofeature-ha'
+        - 'daisy-project-parameter':
+            gs-pathname: '{gs-pathname}'
+        - string:
+            name: DEPLOY_TIMEOUT
+            default: '150'
+            description: 'Deployment timeout in minutes'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+
+    builders:
+        - description-setter:
+            description: "POD: $NODE_NAME"
+        - shell:
+            !include-raw-escape: ./daisy4nfv-download-artifact.sh
+        - shell:
+            !include-raw-escape: ./daisy-deploy.sh
+
+
+########################
+# trigger macros
+########################
+#-----------------------------------------------
+# Triggers for job running on daisy-baremetal against master branch
+#-----------------------------------------------
+# HA Scenarios
+- trigger:
+    name: 'daisy-os-nosdn-nofeature-ha-baremetal-daily-master-trigger'
+    triggers:
+        - timed: ''
+# NOHA Scenarios
+- trigger:
+    name: 'daisy-os-nosdn-nofeature-noha-baremetal-daily-master-trigger'
+    triggers:
+        - timed: ''
+#-----------------------------------------------
+# Triggers for job running on daisy-virtual against master branch
+#-----------------------------------------------
+- trigger:
+    name: 'daisy-os-nosdn-nofeature-ha-virtual-daily-master-trigger'
+    triggers:
+        - timed: ''
+# NOHA Scenarios
+- trigger:
+    name: 'daisy-os-nosdn-nofeature-noha-virtual-daily-master-trigger'
+    triggers:
+        - timed: 'H 8,22 * * *'
+
diff --git a/jjb/daisy4nfv/daisy-deploy.sh b/jjb/daisy4nfv/daisy-deploy.sh
new file mode 100755 (executable)
index 0000000..b512e3f
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/bash
+set -o nounset
+set -o pipefail
+
+echo "--------------------------------------------------------"
+echo "This is $INSTALLER_TYPE deploy job!"
+echo "--------------------------------------------------------"
+
+DEPLOY_SCENARIO=${DEPLOY_SCENARIO:-"os-nosdn-nofeature-ha"}
+BRIDGE=${BRIDGE:-pxebr}
+LAB_NAME=${NODE_NAME/-*}
+POD_NAME=${NODE_NAME/*-}
+deploy_ret=0
+
+if [[ ! "$NODE_NAME" =~ "-virtual" ]] && [[ ! "$LAB_NAME" =~ (zte) ]]; then
+    echo "Unsupported lab $LAB_NAME for now, Cannot continue!"
+    exit $deploy_ret
+fi
+
+# clone the securedlab repo
+cd $WORKSPACE
+BASE_DIR=$(cd ./;pwd)
+
+echo "Cloning securedlab repo $BRANCH"
+git clone ssh://jenkins-zte@gerrit.opnfv.org:29418/securedlab --quiet \
+    --branch $BRANCH
+
+# daisy ci/deploy/deploy.sh use $BASE_DIR/labs dir
+cp -r securedlab/labs .
+
+DEPLOY_COMMAND="sudo ./ci/deploy/deploy.sh -b $BASE_DIR \
+                -l $LAB_NAME -p $POD_NAME -B $BRIDGE"
+
+# log info to console
+echo """
+Deployment parameters
+--------------------------------------------------------
+Scenario: $DEPLOY_SCENARIO
+LAB: $LAB_NAME
+POD: $POD_NAME
+BRIDGE: $BRIDGE
+BASE_DIR: $BASE_DIR
+
+Starting the deployment using $INSTALLER_TYPE. This could take some time...
+--------------------------------------------------------
+Issuing command
+$DEPLOY_COMMAND
+"""
+
+# start the deployment
+$DEPLOY_COMMAND
+
+if [ $? -ne 0 ]; then
+    echo
+    echo "Depolyment failed!"
+    deploy_ret=1
+else
+    echo
+    echo "--------------------------------------------------------"
+    echo "Deployment done!"
+fi
+
+exit $deploy_ret
diff --git a/jjb/daisy4nfv/daisy-project-jobs.yml b/jjb/daisy4nfv/daisy-project-jobs.yml
new file mode 100644 (file)
index 0000000..e631ee9
--- /dev/null
@@ -0,0 +1,232 @@
+######################################################################
+# Add daily jobs, for buidoing, deploying and testing
+# TODO:
+# - [ ] Add yardstick and functest for test stage
+# - [x] Use daisy-baremetal-defauls for choosing baremetal deployment
+######################################################################
+
+#############################
+# Job configuration for daisy
+#############################
+- project:
+    name: daisy-project-jobs
+
+    project: 'daisy'
+
+    installer: 'daisy'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+
+    phase:
+        - 'build':
+            slave-label: 'opnfv-build-centos'
+        - 'deploy':
+            slave-label: 'daisy-baremetal'
+        - 'test':
+            slave-label: 'opnfv-build-centos'
+    jobs:
+        - '{installer}-daily-{stream}'
+        - '{installer}-{phase}-daily-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: '{installer}-daily-{stream}'
+
+    project-type: multijob
+
+    disabled: false
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - '{installer}-daily-.*'
+            block-level: 'NODE'
+
+    scm:
+        - git-scm
+
+    triggers:
+        - timed: '0 H/8 * * *'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-centos-defaults'
+        - 'daisy-defaults'
+        - '{installer}-project-parameter':
+            gs-pathname: '{gs-pathname}'
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: build
+            condition: SUCCESSFUL
+            projects:
+                - name: '{installer}-build-daily-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
+                  node-parameters: false
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: deploy
+            condition: SUCCESSFUL
+            projects:
+                - name: '{installer}-deploy-daily-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
+                  node-parameters: false
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: test
+            condition: SUCCESSFUL
+            projects:
+                - name: '{installer}-test-daily-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
+                  node-parameters: false
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+
+    publishers:
+        - '{installer}-recipients'
+
+- job-template:
+    name: '{installer}-{phase}-daily-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 6
+            option: 'project'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'daisy-defaults'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-nosdn-nofeature-ha'
+        - 'daisy-defaults'
+        - '{slave-label}-defaults'
+        - '{installer}-project-parameter':
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+              description: "Built on $NODE_NAME"
+        - '{installer}-{phase}-daily-macro'
+
+#####################################
+# builder macros
+#####################################
+- builder:
+    name: 'daisy-build-daily-macro'
+    builders:
+        - shell:
+            !include-raw: ./daisy4nfv-basic.sh
+        - shell:
+            !include-raw: ./daisy4nfv-build.sh
+        - shell:
+            !include-raw: ./daisy4nfv-upload-artifact.sh
+        - shell:
+            !include-raw: ./daisy4nfv-workspace-cleanup.sh
+
+- builder:
+    name: 'daisy-deploy-daily-macro'
+    builders:
+        - shell:
+            !include-raw: ./daisy4nfv-download-artifact.sh
+        - shell:
+            !include-raw: ./daisy-deploy.sh
+
+- builder:
+    name: 'daisy-test-daily-macro'
+    builders:
+        - shell: |
+            #!/bin/bash
+
+            echo "Not activated!"
+
+#####################################
+# parameter macros
+#####################################
+- publisher:
+    name: 'daisy-recipients'
+    publishers:
+        - email:
+            recipients: hu.zhijiang@zte.com.cn lu.yao135@zte.com.cn zhou.ya@zte.com.cn yangyang1@zte.com.cn julienjut@gmail.com
+
+- parameter:
+    name: 'daisy-project-parameter'
+    parameters:
+        - string:
+            name: BUILD_DIRECTORY
+            default: $WORKSPACE/build_output
+            description: "Directory where the build artifact will be located upon the completion of the build."
+        - string:
+            name: CACHE_DIRECTORY
+            default: $HOME/opnfv/cache/$INSTALLER_TYPE
+            description: "Directory where the cache to be used during the build is located."
+        - string:
+            name: GS_URL
+            default: artifacts.opnfv.org/$PROJECT{gs-pathname}
+            description: "URL to Google Storage."
index ec11db5..375d807 100755 (executable)
@@ -4,11 +4,32 @@ echo "--------------------------------------------------------"
 echo "This is diasy4nfv build job!"
 echo "--------------------------------------------------------"
 
+# set OPNFV_ARTIFACT_VERSION
+if [[ "$JOB_NAME" =~ "merge" ]]; then
+    echo "Building Daisy4nfv ISO for a merged change"
+    export OPNFV_ARTIFACT_VERSION="gerrit-$GERRIT_CHANGE_NUMBER"
+else
+    export OPNFV_ARTIFACT_VERSION=$(date -u +"%Y-%m-%d_%H-%M-%S")
+fi
+
 # build output directory
 OUTPUT_DIR=$WORKSPACE/build_output
 mkdir -p $OUTPUT_DIR
 
 # start the build
 cd $WORKSPACE
-./ci/build.sh $OUTPUT_DIR
+./ci/build.sh $OUTPUT_DIR $OPNFV_ARTIFACT_VERSION
+
+# save information regarding artifact into file
+(
+    echo "OPNFV_ARTIFACT_VERSION=$OPNFV_ARTIFACT_VERSION"
+    echo "OPNFV_GIT_URL=$(git config --get remote.origin.url)"
+    echo "OPNFV_GIT_SHA1=$(git rev-parse HEAD)"
+    echo "OPNFV_ARTIFACT_URL=$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin"
+    echo "OPNFV_ARTIFACT_SHA512SUM=$(sha512sum $OUTPUT_DIR/opnfv-$OPNFV_ARTIFACT_VERSION.bin | cut -d' ' -f1)"
+    echo "OPNFV_BUILD_URL=$BUILD_URL"
+) > $WORKSPACE/opnfv.properties
 
+echo
+echo "--------------------------------------------------------"
+echo "Done!"
diff --git a/jjb/daisy4nfv/daisy4nfv-download-artifact.sh b/jjb/daisy4nfv/daisy4nfv-download-artifact.sh
new file mode 100755 (executable)
index 0000000..1cc0443
--- /dev/null
@@ -0,0 +1,72 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2016 ZTE Coreporation and others.
+# hu.zhijiang@zte.com.cn
+# sun.jing22@zte.com.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o pipefail
+
+# use proxy url to replace the nomral URL, for googleusercontent.com will be blocked randomly
+[[ "$NODE_NAME" =~ (zte) ]] && GS_URL=${GS_BASE_PROXY%%/*}/$GS_URL
+
+if [[ "$JOB_NAME" =~ "merge" ]]; then
+    echo "Downloading http://$GS_URL/opnfv-gerrit-$GERRIT_CHANGE_NUMBER.properties"
+    # get the properties file for the Daisy4nfv BIN built for a merged change
+    curl -L -s -o $WORKSPACE/latest.properties http://$GS_URL/opnfv-gerrit-$GERRIT_CHANGE_NUMBER.properties
+else
+    # get the latest.properties file in order to get info regarding latest artifact
+    echo "Downloading http://$GS_URL/latest.properties"
+    curl -L -s -o $WORKSPACE/latest.properties http://$GS_URL/latest.properties
+fi
+
+# check if we got the file
+[[ -f $WORKSPACE/latest.properties ]] || exit 1
+
+# source the file so we get artifact metadata
+source $WORKSPACE/latest.properties
+
+# echo the info about artifact that is used during the deployment
+OPNFV_ARTIFACT=${OPNFV_ARTIFACT_URL/*\/}
+echo "Using $OPNFV_ARTIFACT for deployment"
+
+[[ "$NODE_NAME" =~ (zte) ]] && OPNFV_ARTIFACT_URL=${GS_BASE_PROXY%%/*}/$OPNFV_ARTIFACT_URL
+
+if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
+    # check if we already have the image to avoid redownload
+    BINSTORE="/bin_mount/opnfv_ci/${BRANCH##*/}"
+    if [[ -f "$BINSTORE/$OPNFV_ARTIFACT" && ! -z $OPNFV_ARTIFACT_SHA512SUM ]]; then
+        echo "BIN exists locally. Starting to check the sha512sum."
+        if [[ $OPNFV_ARTIFACT_SHA512SUM = $(sha512sum -b $BINSTORE/$OPNFV_ARTIFACT | cut -d' ' -f1) ]]; then
+            echo "Sha512sum is verified. Skipping the download and using the file from BIN store."
+            ln -s $BINSTORE/$OPNFV_ARTIFACT $WORKSPACE/opnfv.bin
+            echo "--------------------------------------------------------"
+            echo
+            ls -al $WORKSPACE/opnfv.bin
+            echo
+            echo "--------------------------------------------------------"
+            echo "Done!"
+            exit 0
+        fi
+    fi
+fi
+
+# log info to console
+echo "Downloading the $INSTALLER_TYPE artifact using URL http://$OPNFV_ARTIFACT_URL"
+echo "This could take some time..."
+echo "--------------------------------------------------------"
+echo
+
+# download the file
+curl -L -s -o $WORKSPACE/opnfv.bin http://$OPNFV_ARTIFACT_URL > gsutil.bin.log 2>&1
+
+# list the file
+ls -al $WORKSPACE/opnfv.bin
+
+echo
+echo "--------------------------------------------------------"
+echo "Done!"
diff --git a/jjb/daisy4nfv/daisy4nfv-merge-jobs.yml b/jjb/daisy4nfv/daisy4nfv-merge-jobs.yml
new file mode 100644 (file)
index 0000000..9e7b867
--- /dev/null
@@ -0,0 +1,218 @@
+- project:
+    name: 'daisy4nfv-merge-jobs'
+
+    project: 'daisy'
+
+    installer: 'daisy'
+
+###########################################################
+# use alias to keep the jobs'name existed already unchanged
+###########################################################
+    alias: 'daisy4nfv'
+
+#####################################
+# branch definitions
+#####################################
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: true
+#####################################
+# patch merge phases
+#####################################
+    phase:
+        - 'build':
+            slave-label: 'opnfv-build-centos'
+        - 'deploy-virtual':
+            slave-label: 'daisy-virtual'
+#####################################
+# jobs
+#####################################
+    jobs:
+        - '{alias}-merge-{stream}'
+        - '{alias}-merge-{phase}-{stream}'
+#####################################
+# job templates
+#####################################
+- job-template:
+    name: '{alias}-merge-{stream}'
+
+    project-type: multijob
+
+    disabled: false
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            option: 'project'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - change-merged-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'remerge'
+            projects:
+                - project-compare-type: 'ANT'
+                  project-pattern: '{project}'
+                  branches:
+                      - branch-compare-type: 'ANT'
+                        branch-pattern: '**/{branch}'
+                  file-paths:
+                      - compare-type: ANT
+                        pattern: 'ci/**'
+                      - compare-type: ANT
+                        pattern: 'code/**'
+                      - compare-type: ANT
+                        pattern: 'deploy/**'
+                  disable-strict-forbidden-file-verification: 'true'
+                  forbidden-file-paths:
+                      - compare-type: ANT
+                        pattern: 'docs/**'
+                      - compare-type: ANT
+                        pattern: '.gitignore'
+            readable-message: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-centos-defaults'
+        - '{alias}-merge-defaults':
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: build
+            condition: SUCCESSFUL
+            projects:
+                - name: '{alias}-merge-build-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
+                  node-parameters: false
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: deploy-virtual
+            condition: SUCCESSFUL
+            projects:
+                - name: '{alias}-merge-deploy-virtual-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    BRANCH=$BRANCH
+                    GERRIT_REFSPEC=$GERRIT_REFSPEC
+                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
+                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
+                  node-parameters: false
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+
+- job-template:
+    name: '{alias}-merge-{phase}-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - '{alias}-merge-(master|danube)'
+            block-level: 'NODE'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{slave-label}-defaults'
+        - '{alias}-merge-defaults':
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - '{project}-merge-{phase}-macro'
+
+#####################################
+# builder macros
+#####################################
+- builder:
+    name: 'daisy-merge-build-macro'
+    builders:
+        - shell:
+            !include-raw: ./daisy4nfv-basic.sh
+        - shell:
+            !include-raw: ./daisy4nfv-build.sh
+        - shell:
+            !include-raw: ./daisy4nfv-upload-artifact.sh
+        - shell:
+            !include-raw: ./daisy4nfv-workspace-cleanup.sh
+
+- builder:
+    name: 'daisy-merge-deploy-virtual-macro'
+    builders:
+        - shell:
+            !include-raw: ./daisy4nfv-download-artifact.sh
+        - shell:
+            !include-raw: ./daisy-deploy.sh
+        - shell:
+            !include-raw: ./daisy4nfv-workspace-cleanup.sh
+
+#####################################
+# parameter macros
+#####################################
+- parameter:
+    name: 'daisy4nfv-merge-defaults'
+    parameters:
+        - string:
+            name: BUILD_DIRECTORY
+            default: $WORKSPACE/build_output
+            description: "Directory where the build artifact will be located upon the completion of the build."
+        - string:
+            name: CACHE_DIRECTORY
+            default: $HOME/opnfv/cache/$INSTALLER_TYPE
+            description: "Directory where the cache to be used during the build is located."
+        - string:
+            name: GS_URL
+            default: artifacts.opnfv.org/$PROJECT{gs-pathname}
+            description: "URL to Google Storage."
diff --git a/jjb/daisy4nfv/daisy4nfv-upload-artifact.sh b/jjb/daisy4nfv/daisy4nfv-upload-artifact.sh
new file mode 100755 (executable)
index 0000000..6b0aec5
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2016 ZTE Coreporation and others.
+# hu.zhijiang@zte.com.cn
+# sun.jing22@zte.com.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o pipefail
+
+# check if we built something
+if [ -f $WORKSPACE/.noupload ]; then
+    echo "Nothing new to upload. Exiting."
+    /bin/rm -f $WORKSPACE/.noupload
+    exit 0
+fi
+
+# source the opnfv.properties to get ARTIFACT_VERSION
+source $WORKSPACE/opnfv.properties
+
+importkey () {
+# clone releng repository
+echo "Cloning releng repository..."
+[ -d releng ] && rm -rf releng
+git clone https://gerrit.opnfv.org/gerrit/releng $WORKSPACE/releng/ &> /dev/null
+#this is where we import the siging key
+if [ -f $WORKSPACE/releng/utils/gpg_import_key.sh ]; then
+  source $WORKSPACE/releng/utils/gpg_import_key.sh
+fi
+}
+
+signbin () {
+gpg2 -vvv --batch --yes --no-tty \
+  --default-key opnfv-helpdesk@rt.linuxfoundation.org  \
+  --passphrase besteffort \
+  --detach-sig $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.bin
+
+gsutil cp $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.bin.sig gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin.sig
+echo "BIN signature Upload Complete!"
+}
+
+uploadbin () {
+# log info to console
+echo "Uploading $INSTALLER_TYPE artifact. This could take some time..."
+echo
+
+cd $WORKSPACE
+# upload artifact and additional files to google storage
+gsutil cp $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.bin \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin > gsutil.bin.log 2>&1
+gsutil cp $WORKSPACE/opnfv.properties \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.properties > gsutil.properties.log 2>&1
+if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
+    gsutil cp $WORKSPACE/opnfv.properties \
+    gs://$GS_URL/latest.properties > gsutil.latest.log 2>&1
+elif [[ "$JOB_NAME" =~ "merge" ]]; then
+    echo "Uploaded Daisy4nfv BIN for a merged change"
+fi
+
+gsutil -m setmeta \
+    -h "Content-Type:text/html" \
+    -h "Cache-Control:private, max-age=0, no-transform" \
+    gs://$GS_URL/latest.properties \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.properties > /dev/null 2>&1
+
+gsutil -m setmeta \
+    -h "Cache-Control:private, max-age=0, no-transform" \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin > /dev/null 2>&1
+
+# disabled errexit due to gsutil setmeta complaints
+#   BadRequestException: 400 Invalid argument
+# check if we uploaded the file successfully to see if things are fine
+gsutil ls gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin > /dev/null 2>&1
+if [[ $? -ne 0 ]]; then
+    echo "Problem while uploading artifact!"
+    echo "Check log $WORKSPACE/gsutil.bin.log on the machine where this build is done."
+    exit 1
+fi
+
+echo "Done!"
+echo
+echo "--------------------------------------------------------"
+echo
+echo "Artifact is available as http://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin"
+echo
+echo "--------------------------------------------------------"
+echo
+}
+
+importkey
+signbin
+uploadbin
index d2adafd..a0ec2eb 100644 (file)
@@ -1,7 +1,12 @@
 - project:
     name: 'daisy4nfv-verify-jobs'
-
     project: 'daisy'
+    installer: 'daisy'
+##########################################################
+# use alias to keep the jobs'name existed alread unchanged
+##########################################################
+    alias: 'daisy4nfv'
+
 #####################################
 # branch definitions
 #####################################
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
 #####################################
 # patch verification phases
 #####################################
     phase:
-        - 'basic':
+        - unit:
             slave-label: 'opnfv-build'
-        - 'build':
+        - build:
             slave-label: 'opnfv-build-centos'
-        - 'deploy-virtual':
-            slave-label: 'opnfv-build'
-        - 'smoke-test':
-            slave-label: 'opnfv-build'
 #####################################
 # jobs
 #####################################
     jobs:
-        - 'daisy4nfv-verify-{stream}'
-        - 'daisy4nfv-verify-{phase}-{stream}'
+        - '{alias}-verify-{stream}'
+        - '{alias}-verify-{phase}-{stream}'
 #####################################
 # job templates
 #####################################
 - job-template:
-    name: 'daisy4nfv-verify-{stream}'
-
+    name: '{alias}-verify-{stream}'
     project-type: multijob
-
     disabled: false
-
     concurrent: true
-
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             option: 'project'
-
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
-
+        - git-scm
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
-
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 - comment-added-contains-event:
                     comment-contains-value: 'reverify'
             projects:
-              - project-compare-type: 'ANT'
-                project-pattern: '{project}'
-                branches:
-                  - branch-compare-type: 'ANT'
-                    branch-pattern: '**/{branch}'
-                forbidden-file-paths:
-                  - compare-type: ANT
-                    pattern: 'docs/**|.gitignore'
+                - project-compare-type: 'ANT'
+                  project-pattern: '{project}'
+                  branches:
+                      - branch-compare-type: 'ANT'
+                        branch-pattern: '**/{branch}'
+                  file-paths:
+                      - compare-type: ANT
+                        pattern: 'ci/**'
+                      - compare-type: ANT
+                        pattern: 'code/**'
+                      - compare-type: ANT
+                        pattern: 'deploy/**'
+                  disable-strict-forbidden-file-verification: 'true'
+                  forbidden-file-paths:
+                      - compare-type: ANT
+                        pattern: 'docs/**'
+                      - compare-type: ANT
+                        pattern: '.gitignore'
             readable-message: true
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
-        - 'opnfv-build-defaults'
-        - 'daisy4nfv-verify-defaults':
+        - 'opnfv-build-centos-defaults'
+        - '{alias}-verify-defaults':
             gs-pathname: '{gs-pathname}'
 
     builders:
         - description-setter:
             description: "Built on $NODE_NAME"
         - multijob:
-            name: basic
+            name: unit
             condition: SUCCESSFUL
             projects:
-                - name: 'daisy4nfv-verify-basic-{stream}'
-                  current-parameters: false
-                  predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
-                    GERRIT_REFSPEC=$GERRIT_REFSPEC
-                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
-                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
+                - name: '{alias}-verify-{name}-{stream}'
+                  current-parameters: true
                   node-parameters: false
                   kill-phase-on: FAILURE
                   abort-all-job: true
             name: build
             condition: SUCCESSFUL
             projects:
-                - name: 'daisy4nfv-verify-build-{stream}'
-                  current-parameters: false
-                  predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
-                    GERRIT_REFSPEC=$GERRIT_REFSPEC
-                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
-                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
-                  node-parameters: false
-                  kill-phase-on: FAILURE
-                  abort-all-job: true
-        - multijob:
-            name: deploy-virtual
-            condition: SUCCESSFUL
-            projects:
-                - name: 'daisy4nfv-verify-deploy-virtual-{stream}'
+                - name: '{alias}-verify-build-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
-                    GERRIT_REFSPEC=$GERRIT_REFSPEC
-                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
-                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
-                  node-parameters: false
-                  kill-phase-on: FAILURE
-                  abort-all-job: true
-        - multijob:
-            name: smoke-test
-            condition: SUCCESSFUL
-            projects:
-                - name: 'daisy4nfv-verify-smoke-test-{stream}'
-                  current-parameters: false
-                  predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                   abort-all-job: true
 
 - job-template:
-    name: 'daisy4nfv-verify-{phase}-{stream}'
-
+    name: '{alias}-verify-{phase}-{stream}'
     disabled: '{obj:disabled}'
-
     concurrent: true
-
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 6
             option: 'project'
-        - build-blocker:
-            use-build-blocker: true
-            blocking-jobs:
-                - 'daisy4nfv-verify-deploy-.*'
-                - 'daisy4nfv-verify-test-.*'
-            block-level: 'NODE'
-
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
-
+        - git-scm
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
-
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
-        - 'daisy4nfv-verify-defaults':
+        - '{alias}-verify-defaults':
             gs-pathname: '{gs-pathname}'
-
     builders:
         - description-setter:
             description: "Built on $NODE_NAME"
         - '{project}-verify-{phase}-macro'
+
 #####################################
 # builder macros
 #####################################
 - builder:
-    name: 'daisy-verify-basic-macro'
+    name: 'daisy-verify-build-macro'
     builders:
         - shell:
             !include-raw: ./daisy4nfv-basic.sh
-
-- builder:
-    name: 'daisy-verify-build-macro'
-    builders:
         - shell:
             !include-raw: ./daisy4nfv-build.sh
-
-- builder:
-    name: 'daisy-verify-deploy-virtual-macro'
-    builders:
         - shell:
-            !include-raw: ./daisy4nfv-virtual-deploy.sh
+            !include-raw: ./daisy4nfv-workspace-cleanup.sh
 
 - builder:
-    name: 'daisy-verify-smoke-test-macro'
+    name: daisy-verify-unit-macro
     builders:
         - shell: |
             #!/bin/bash
+            set -o errexit
+            set -o pipefail
+            set -o xtrace
+            tox -e py27
 
-            echo "Not activated!"
 #####################################
 # parameter macros
 #####################################
diff --git a/jjb/daisy4nfv/daisy4nfv-workspace-cleanup.sh b/jjb/daisy4nfv/daisy4nfv-workspace-cleanup.sh
new file mode 100755 (executable)
index 0000000..26f7e9a
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2016 ZTE Coreporation and others.
+# hu.zhijiang@zte.com.cn
+# sun.jing22@zte.com.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o nounset
+set -o pipefail
+
+# delete the $WORKSPACE to open some space
+/bin/rm -rf $WORKSPACE
index 4958ca2..c677ef9 100644 (file)
@@ -7,22 +7,40 @@
         - master:
             branch: '{stream}'
             gs-pathname: ''
-            docker_tag: 'master'
+            docker-tag: 'latest'
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
-            docker_tag: 'stable'
+            docker-tag: 'stable'
             disabled: false
 
     installer:
         - apex:
             slave-label: 'ool-virtual1'
             pod: 'ool-virtual1'
+        - fuel:
+            slave-label: 'ool-virtual2'
+            pod: 'ool-virtual2'
+        #- joid:
+        #    slave-label: 'ool-virtual3'
+        #    pod: 'ool-virtual3'
+
+    inspector:
+        - 'sample'
+        - 'congress'
+
+    task:
+        - verify:
+            profiler: 'none'
+            auto-trigger-name: 'doctor-verify'
+        - profiling:
+            profiler: 'poc'
+            auto-trigger-name: 'experimental'
 
     jobs:
         - 'doctor-verify-{stream}'
-        - 'doctor-verify-{installer}-{stream}'
+        - 'doctor-{task}-{installer}-{inspector}-{stream}'
 
 - job-template:
     name: 'doctor-verify-{stream}'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
         - shell: "[ -e tests/run.sh ] && bash -n ./tests/run.sh"
 
 - job-template:
-    name: 'doctor-verify-{installer}-{stream}'
+    name: 'doctor-{task}-{installer}-{inspector}-{stream}'
 
     node: '{slave-label}'
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: OS_CREDS
             default: /home/jenkins/openstack.creds
             description: 'OpenStack credentials'
         - '{slave-label}-defaults'
+        - '{installer}-defaults'
+        - string:
+            name: DOCKER_TAG
+            default: '{docker-tag}'
+            description: 'Tag to pull docker image'
+        - string:
+            name: CLEAN_DOCKER_IMAGES
+            default: 'false'
+            description: 'Remove downloaded docker images (opnfv/functest:*)'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-nosdn-nofeature-ha'
+            description: 'Scenario to deploy and test'
+        # functest-suite-parameter
+        - string:
+            name: FUNCTEST_SUITE_NAME
+            default: '{project}'
+        - string:
+            name: TESTCASE_OPTIONS
+            default: '-e INSPECTOR_TYPE={inspector} -e PROFILER_TYPE={profiler} -v $WORKSPACE:/home/opnfv/repos/doctor'
+            description: 'Addtional parameters specific to test case(s)'
+        # functest-parameter
+        - string:
+            name: GS_PATHNAME
+            default: '{gs-pathname}'
+            description: "Version directory where the opnfv documents will be stored in gs repository"
+        - string:
+            name: FUNCTEST_REPO_DIR
+            default: "/home/opnfv/repos/functest"
+            description: "Directory where the Functest repository is cloned"
+        - string:
+            name: PUSH_RESULTS_TO_DB
+            default: "true"
+            description: "Push the results of all the tests to the resultDB"
+        - string:
+            name: CI_DEBUG
+            default: 'true'
+            description: "Show debug output information"
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
+
+    triggers:
+        - '{auto-trigger-name}':
+            project: '{project}'
+            branch: '{branch}'
+
+    builders:
+        - 'clean-workspace-log'
+        - shell: |
+            # NOTE: Create symbolic link, so that we can archive file outside
+            #       of $WORKSPACE .
+            # NOTE: We are printing all logs under 'tests/' during test run,
+            #       so this symbolic link should not be in 'tests/'. Otherwise,
+            #       we'll have the same log twice in jenkins console log.
+            ln -sfn $HOME/opnfv/functest/results/{stream} functest_results
+        - 'functest-suite-builder'
+        - shell: |
+            functest_log="$HOME/opnfv/functest/results/{stream}/{project}.log"
+            # NOTE: checking the test result, as the previous job could return
+            #       0 regardless the result of doctor test scenario.
+            grep -e ' OK$' $functest_log || exit 1
+
+    publishers:
+        - archive:
+            artifacts: 'tests/*.log'
+        - archive:
+            artifacts: 'functest_results/{project}.log'
 
+
+#####################################
+# trigger macros
+#####################################
+- trigger:
+    name: 'doctor-verify'
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 failed: true
                 unstable: true
                 notbuilt: true
-
-    builders:
-        - trigger-builds:
-          - project: 'functest-{installer}-{pod}-suite-{stream}'
-            current-parameters: true
-            predefined-parameters: |
-              CI_DEBUG=true
-              FUNCTEST_SUITE_NAME=doctor
-              DEPLOY_SCENARIO=os-nosdn-nofeature-ha
-              TESTCASE_OPTIONS=-e INSPECTOR_TYPE=sample -v $WORKSPACE:$HOME/opnfv/repos/doctor
-            block: true
-            same-node: true
-          - project: 'functest-{installer}-{pod}-suite-{stream}'
-            current-parameters: true
-            predefined-parameters: |
-              CI_DEBUG=true
-              FUNCTEST_SUITE_NAME=doctor
-              DEPLOY_SCENARIO=os-nosdn-nofeature-ha
-              TESTCASE_OPTIONS=-e INSPECTOR_TYPE=congress -v $WORKSPACE:$HOME/opnfv/repos/doctor
-            block: true
-            same-node: true
-
-    publishers:
-        - postbuildscript:
-            builders:
-                - functest-copy-suite-log:
-                    suite: '{project}'
-        - archive:
-            artifacts: '{project}.log'
-
-- builder:
-    name: functest-copy-suite-log
-    builders:
-        - shell: |
-            cp $HOME/opnfv/functest/results/${{GIT_BRANCH##*/}}/{suite}.log $WORKSPACE/
index 7cee984..8c9be12 100644 (file)
@@ -11,7 +11,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -52,6 +49,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
diff --git a/jjb/dovetail/dovetail-artifacts-upload.sh b/jjb/dovetail/dovetail-artifacts-upload.sh
new file mode 100755 (executable)
index 0000000..b23deca
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o pipefail
+
+echo "dovetail: pull and save the images"
+
+[[ -d ${CACHE_DIR} ]] || mkdir -p ${CACHE_DIR}
+
+cd ${CACHE_DIR}
+sudo docker pull ${DOCKER_REPO_NAME}:${DOCKER_TAG}
+sudo docker save -o ${STORE_FILE_NAME} ${DOCKER_REPO_NAME}:${DOCKER_TAG}
+sudo chmod og+rw ${STORE_FILE_NAME}
+
+OPNFV_ARTIFACT_VERSION=$(date -u +"%Y-%m-%d_%H-%M-%S")
+GS_UPLOAD_LOCATION="${STORE_URL}/${OPNFV_ARTIFACT_VERSION}"
+(
+    echo "OPNFV_ARTIFACT_VERSION=$OPNFV_ARTIFACT_VERSION"
+    echo "OPNFV_GIT_URL=$(git config --get remote.origin.url)"
+    echo "OPNFV_GIT_SHA1=$(git rev-parse HEAD)"
+    echo "OPNFV_ARTIFACT_URL=$GS_UPLOAD_LOCATION"
+    echo "OPNFV_BUILD_URL=$BUILD_URL"
+) > $WORKSPACE/opnfv.properties
+source $WORKSPACE/opnfv.properties
+
+importkey () {
+# clone releng repository
+echo "Cloning releng repository..."
+[ -d releng ] && rm -rf releng
+git clone https://gerrit.opnfv.org/gerrit/releng $WORKSPACE/releng/ &> /dev/null
+#this is where we import the siging key
+if [ -f $WORKSPACE/releng/utils/gpg_import_key.sh ]; then
+  source $WORKSPACE/releng/utils/gpg_import_key.sh
+fi
+}
+
+sign () {
+gpg2 -vvv --batch --yes --no-tty \
+  --default-key opnfv-helpdesk@rt.linuxfoundation.org  \
+  --passphrase besteffort \
+  --detach-sig ${CACHE_DIR}/${STORE_FILE_NAME}
+
+gsutil cp ${CACHE_DIR}/${STORE_FILE_NAME}.sig ${STORE_URL}/${STORE_FILE_NAME}.sig
+echo "signature Upload Complete!"
+}
+
+upload () {
+# log info to console
+echo "Uploading to artifact. This could take some time..."
+echo
+
+cd $WORKSPACE
+# upload artifact and additional files to google storage
+gsutil cp ${CACHE_DIR}/${STORE_FILE_NAME} \
+${STORE_URL}/${STORE_FILE_NAME} > gsutil.dockerfile.log 2>&1
+gsutil cp $WORKSPACE/opnfv.properties \
+${STORE_URL}/opnfv-$OPNFV_ARTIFACT_VERSION.properties > gsutil.properties.log 2>&1
+gsutil cp $WORKSPACE/opnfv.properties \
+    ${STORE_URL}/latest.properties > gsutil.latest.log 2>&1
+
+gsutil -m setmeta \
+    -h "Content-Type:text/html" \
+    -h "Cache-Control:private, max-age=0, no-transform" \
+    ${STORE_URL}/latest.properties \
+    ${STORE_URL}/opnfv-$OPNFV_ARTIFACT_VERSION.properties > /dev/null 2>&1
+
+gsutil -m setmeta \
+    -h "Cache-Control:private, max-age=0, no-transform" \
+    ${STORE_URL}/${STORE_FILE_NAME} > /dev/null 2>&1
+
+# disabled errexit due to gsutil setmeta complaints
+#   BadRequestException: 400 Invalid argument
+# check if we uploaded the file successfully to see if things are fine
+gsutil ls ${STORE_URL}/${STORE_FILE_NAME} > /dev/null 2>&1
+if [[ $? -ne 0 ]]; then
+    echo "Problem while uploading artifact!"
+    exit 1
+fi
+
+echo "dovetail: uploading Done!"
+echo
+echo "--------------------------------------------------------"
+echo
+}
+
+#importkey
+#sign
+upload
diff --git a/jjb/dovetail/dovetail-artifacts-upload.yml b/jjb/dovetail/dovetail-artifacts-upload.yml
new file mode 100644 (file)
index 0000000..3d9af5e
--- /dev/null
@@ -0,0 +1,130 @@
+############################################
+# dovetail upload artifacts job
+############################################
+- project:
+    name: dovetail-artifacts-upload
+
+    project: 'dovetail'
+
+    jobs:
+        - 'dovetail-{image}-artifacts-upload-{stream}'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+
+    image:
+        - 'dovetail'
+        - 'functest'
+        - 'yardstick'
+
+#############################################
+# job template
+#############################################
+
+- job-template:
+    name: 'dovetail-{image}-artifacts-upload-{stream}'
+
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 1
+            max-per-node: 1
+            option: 'project'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+        - dovetail-parameter:
+            gs-pathname: '{gs-pathname}'
+            image: '{image}'
+            branch: '{branch}'
+
+    scm:
+        - git-scm
+
+    builders:
+        - 'dovetail-builder-artifacts-upload'
+        - 'dovetail-workspace-cleanup'
+
+####################
+# parameter macros
+####################
+- parameter:
+    name: dovetail-parameter
+    parameters:
+        - string:
+            name: CACHE_DIR
+            default: $WORKSPACE/cache{gs-pathname}
+            description: "the cache to store packages downloaded"
+        - string:
+            name: STORE_URL
+            default: gs://artifacts.opnfv.org/dovetail{gs-pathname}
+            description: "LF artifacts url for storage of dovetail packages"
+        - string:
+            name: DOCKER_REPO_NAME
+            default: opnfv/{image}
+            description: "docker repo name"
+        - string:
+            name: DOCKER_TAG
+            default: latest
+            description: "docker image tag of which will be uploaded to artifacts"
+        - string:
+            name: STORE_FILE_NAME
+            default: image_{image}_{branch}_$BUILD_ID.docker
+            description: "stored file name"
+
+####################################
+#builders for dovetail project
+####################################
+- builder:
+    name: dovetail-builder-artifacts-upload
+    builders:
+        - shell:
+            !include-raw: ./dovetail-artifacts-upload.sh
+
+- builder:
+    name: dovetail-workspace-cleanup
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+
+            echo "Dovetail: cleanup cache used for storage downloaded packages"
+
+            /bin/rm -rf $CACHE_DIR
+
+            # Remove previous running containers if exist
+            if [[ -n "$(docker ps -a | grep $DOCKER_REPO_NAME)" ]]; then
+                echo "Removing existing $DOCKER_REPO_NAME containers..."
+                docker ps -a | grep $DOCKER_REPO_NAME | awk '{print $1}' | xargs docker rm -f
+                t=60
+                # Wait max 60 sec for containers to be removed
+                while [[ $t -gt 0 ]] && [[ -n "$(docker ps| grep $DOCKER_REPO_NAME)" ]]; do
+                    sleep 1
+                    let t=t-1
+                done
+            fi
+
+            # Remove existing images if exist
+            if [[ -n "$(docker images | grep $DOCKER_REPO_NAME)" ]]; then
+                echo "Docker images to remove:"
+                docker images | head -1 && docker images | grep $DOCKER_REPO_NAME
+                image_tags=($(docker images | grep $DOCKER_REPO_NAME | awk '{print $2}'))
+                for tag in "${image_tags[@]}"; do
+                    if [[ -n "$(docker images|grep $DOCKER_REPO_NAME|grep $tag)" ]]; then
+                        echo "Removing docker image $DOCKER_REPO_NAME:$tag..."
+                        docker rmi -f $DOCKER_REPO_NAME:$tag
+                    fi
+                done
+            fi
index 2921200..8690480 100644 (file)
@@ -20,8 +20,8 @@
         dovetail-branch: '{stream}'
         gs-pathname: ''
         docker-tag: 'latest'
-    colorado: &colorado
-        stream: colorado
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         dovetail-branch: master
         gs-pathname: '/{stream}'
             slave-label: fuel-baremetal
             SUT: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: fuel-virtual
             SUT: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 #compass CI PODs
         - baremetal:
             slave-label: compass-baremetal
             slave-label: compass-baremetal
             SUT: compass
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: compass-virtual
             SUT: compass
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
-#apex CI PODs
-        - apex-verify-master:
+            <<: *danube
+#--------------------------------
+#    Installers not using labels
+#            CI PODs
+# This section should only contain the installers
+# that have not been switched using labels for slaves
+#--------------------------------
+#apex PODs
+        - lf-pod1:
             slave-label: '{pod}'
             SUT: apex
             auto-trigger-name: 'daily-trigger-disabled'
             <<: *master
-        - apex-daily-master:
+        - lf-pod1:
             slave-label: '{pod}'
             SUT: apex
             auto-trigger-name: 'daily-trigger-disabled'
+            <<: *danube
+#armband CI PODs
+        - armband-baremetal:
+            slave-label: armband-baremetal
+            SUT: fuel
+            auto-trigger-name: 'daily-trigger-disabled'
             <<: *master
-        - apex-verify-colorado:
-            slave-label: '{pod}'
-            SUT: apex
+        - armband-virtual:
+            slave-label: armband-virtual
+            SUT: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
-        - apex-daily-colorado:
-            slave-label: '{pod}'
-            SUT: apex
+            <<: *master
+        - armband-baremetal:
+            slave-label: armband-baremetal
+            SUT: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
+        - armband-virtual:
+            slave-label: armband-virtual
+            SUT: fuel
+            auto-trigger-name: 'daily-trigger-disabled'
+            <<: *danube
 #--------------------------------
 #        None-CI PODs
 #--------------------------------
-        - huawei-pod5:
-            slave-label: '{pod}'
+        - baremetal-centos:
+            slave-label: 'intel-pod8'
             SUT: compass
             auto-trigger-name: 'daily-trigger-disabled'
             <<: *master
+        - arm-pod2:
+            slave-label: '{pod}'
+            SUT: fuel
+            auto-trigger-name: 'daily-trigger-disabled'
+            <<: *master
+        - arm-pod3:
+            slave-label: '{pod}'
+            SUT: fuel
+            auto-trigger-name: 'daily-trigger-disabled'
+            <<: *master
 #--------------------------------
     testsuite:
-        - 'basic'
+        - 'debug'
+        - 'compliance_set'
 
     jobs:
         - 'dovetail-{SUT}-{pod}-{testsuite}-{stream}'
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
         - timeout:
             timeout: 180
             abort: true
+        - fix-workspace-permissions
 
     triggers:
         - '{auto-trigger-name}'
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{dovetail-branch}'
         - '{SUT}-defaults'
         - '{slave-label}-defaults'
         - string:
             name: CI_DEBUG
             default: 'true'
             description: "Show debug output information"
+        - string:
+            name: TESTSUITE
+            default: '{testsuite}'
+            description: "dovetail testsuite to run"
+        - string:
+            name: DOVETAIL_REPO_DIR
+            default: "/home/opnfv/dovetail"
+            description: "Directory where the dovetail repository is cloned"
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{dovetail-branch}'
+        - git-scm
 
     builders:
         - description-setter:
             description: "POD: $NODE_NAME"
         - 'dovetail-cleanup'
-        - 'dovetail-{testsuite}'
+        - 'dovetail-run'
 
     publishers:
         - archive:
             allow-empty: true
             fingerprint: true
 
-########################
+#--------------------------
 # builder macros
-########################
+#--------------------------
 - builder:
-    name: dovetail-basic
+    name: dovetail-run
     builders:
         - shell:
             !include-raw: ./dovetail-run.sh
 
-- builder:
-    name: dovetail-fetch-os-creds
-    builders:
-        - shell:
-            !include-raw: ../../utils/fetch_os_creds.sh
-
 - builder:
     name: dovetail-cleanup
     builders:
index 297222b..22b2ba2 100755 (executable)
@@ -1,20 +1,42 @@
 #!/bin/bash
+
 [[ $CI_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
 
-echo "Cleaning up docker containers/images..."
-# Remove previous running containers if exist
+#clean up dependent project docker images, which has no containers and image tag None
+clean_images=(opnfv/functest opnfv/yardstick)
+for clean_image in "${clean_images[@]}"; do
+    echo "Removing image $image_id, which has no containers and image tag is None"
+    dangling_images=($(docker images -f "dangling=true" | grep ${clean_image} | awk '{print $3}'))
+    if [[ -n ${dangling_images} ]]; then
+        for image_id in "${dangling_images[@]}"; do
+            docker rmi $image_id >${redirect}
+        done
+    fi
+done
+
+echo "Remove containers with image dovetail:<None>..."
+dangling_images=($(docker images -f "dangling=true" | grep opnfv/dovetail | awk '{print $3}'))
+if [[ -n ${dangling_images} ]]; then
+    for image_id in "${dangling_images[@]}"; do
+        echo "Removing image $image_id with tag None and its related containers"
+        docker ps -a | grep $image_id | awk '{print $1}'| xargs docker rm -f >${redirect}
+        docker rmi $image_id >${redirect}
+    done
+fi
+
+echo "Cleaning up dovetail docker containers/images..."
 if [[ ! -z $(docker ps -a | grep opnfv/dovetail) ]]; then
     echo "Removing existing opnfv/dovetail containers..."
-    docker ps -a | grep opnfv/dovetail | awk '{print $1}' | xargs docker rm -f >$redirect
+    docker ps -a | grep opnfv/dovetail | awk '{print $1}' | xargs docker rm -f >${redirect}
 fi
 
-# Remove existing images if exist
+echo "Remove dovetail existing images if exist..."
 if [[ ! -z $(docker images | grep opnfv/dovetail) ]]; then
     echo "Docker images to remove:"
-    docker images | head -1 && docker images | grep opnfv/dovetail
+    docker images | head -1 && docker images | grep opnfv/dovetail >${redirect}
     image_tags=($(docker images | grep opnfv/dovetail | awk '{print $2}'))
     for tag in "${image_tags[@]}"; do
         echo "Removing docker image opnfv/dovetail:$tag..."
-        docker rmi opnfv/dovetail:$tag >$redirect
+        docker rmi opnfv/dovetail:$tag >${redirect}
     done
 fi
index 41fd8cd..9dc4808 100644 (file)
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
 #builders for dovetail project
 ###############################
 - builder:
-    name: dovetail-unit-tests
+    name: dovetail-hello-world
     builders:
         - shell: |
             #!/bin/bash
             set -o errexit
-            set -o pipefail
 
-            echo "Running unit tests..."
-            cd $WORKSPACE
-            virtualenv $WORKSPACE/dovetail_venv
-            source $WORKSPACE/dovetail_venv/bin/activate
+            echo "hello world"
 
-            #packages installation
-            easy_install -U setuptools
-            easy_install -U pip
-            pip install -r unittests/requirements.txt
-            pip install -e .
 
-            #unit tests
-            /bin/bash $WORKSPACE/unittests/unittest.sh
+- builder:
+    name: dovetail-unit-tests
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o pipefail
 
-            deactivate
+            tox
index 098b7db..5161a3c 100755 (executable)
@@ -6,9 +6,9 @@
 set -e
 [[ $CI_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
 
-# labconfig is used only for joid
-labconfig=""
 sshkey=""
+# The path of openrc.sh is defined in fetch_os_creds.sh
+OPENRC=$WORKSPACE/opnfv-openrc.sh
 if [[ ${INSTALLER_TYPE} == 'apex' ]]; then
     instack_mac=$(sudo virsh domiflist undercloud | grep default | \
                   grep -Eo "[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+")
@@ -22,7 +22,7 @@ if [[ ${INSTALLER_TYPE} == 'apex' ]]; then
 elif [[ ${INSTALLER_TYPE} == 'joid' ]]; then
     # If production lab then creds may be retrieved dynamically
     # creds are on the jumphost, always in the same folder
-    labconfig="-v $LAB_CONFIG/admin-openrc:/home/opnfv/openrc"
+    sudo cp $LAB_CONFIG/admin-openrc $OPENRC
     # If dev lab, credentials may not be the default ones, just provide a path to put them into docker
     # replace the default one by the customized one provided by jenkins config
 fi
@@ -32,28 +32,64 @@ if ! sudo iptables -C FORWARD -j RETURN 2> ${redirect} || ! sudo iptables -L FOR
     sudo iptables -I FORWARD -j RETURN
 fi
 
-opts="--privileged=true --rm"
-envs="-e CI_DEBUG=${CI_DEBUG} \
-      -e INSTALLER_TYPE=${INSTALLER_TYPE} \
-      -e INSTALLER_IP=${INSTALLER_IP} \
-      -e DEPLOY_SCENARIO=${DEPLOY_SCENARIO} \
-      -e DEPLOY_TYPE=${DEPLOY_TYPE} \
-      -v /var/run/docker.sock:/var/run/docker.sock \
-      -v /home/opnfv/dovetail/results:/home/opnfv/dovetail/results"
+if [[ ${INSTALLER_TYPE} != 'joid' ]]; then
+    releng_repo=${WORKSPACE}/releng
+    [ -d ${releng_repo} ] && sudo rm -rf ${releng_repo}
+    git clone https://gerrit.opnfv.org/gerrit/releng ${releng_repo} >/dev/null
+    ${releng_repo}/utils/fetch_os_creds.sh -d ${OPENRC} -i ${INSTALLER_TYPE} -a ${INSTALLER_IP} >${redirect}
+fi
+
+if [[ -f $OPENRC ]]; then
+    echo "INFO: openstack credentials path is $OPENRC"
+    cat $OPENRC
+else
+    echo "ERROR: file $OPENRC does not exist."
+    exit 1
+fi
+
+opts="--privileged=true -id"
+results_envs="-v /var/run/docker.sock:/var/run/docker.sock \
+              -v /home/opnfv/dovetail/results:/home/opnfv/dovetail/results"
+openrc_volume="-v ${OPENRC}:${OPENRC}"
 
 # Pull the image with correct tag
 echo "Dovetail: Pulling image opnfv/dovetail:${DOCKER_TAG}"
 docker pull opnfv/dovetail:$DOCKER_TAG >$redirect
 
-# Run docker
-echo "Dovetail: docker running..."
-sudo docker run ${opts} ${envs} ${labconfig} ${sshkey} opnfv/dovetail:${DOCKER_TAG} \
-"/home/opnfv/dovetail/dovetail/run.py"
+cmd="docker run ${opts} ${results_envs} ${openrc_volume} \
+     ${sshkey} opnfv/dovetail:${DOCKER_TAG} /bin/bash"
+echo "Dovetail: running docker run command: ${cmd}"
+${cmd} >${redirect}
+sleep 5
+container_id=$(docker ps | grep "opnfv/dovetail:${DOCKER_TAG}" | awk '{print $1}' | head -1)
+echo "Container ID=${container_id}"
+if [ -z ${container_id} ]; then
+    echo "Cannot find opnfv/dovetail container ID ${container_id}. Please check if it is existing."
+    docker ps -a
+    exit 1
+fi
+echo "Container Start: docker start ${container_id}"
+docker start ${container_id}
+sleep 5
+docker ps >${redirect}
+if [ $(docker ps | grep "opnfv/dovetail:${DOCKER_TAG}" | wc -l) == 0 ]; then
+    echo "The container opnfv/dovetail with ID=${container_id} has not been properly started. Exiting..."
+    exit 1
+fi
 
-echo "Dovetail: store results..."
-sudo cp -r /home/opnfv/dovetail/results ./
-#To make sure the file owner is jenkins, for the copied results files in the above line
-#if not, there will be error when next time to wipe workspace
-sudo chown -R jenkins:jenkins ${WORKSPACE}/results
+list_cmd="dovetail list ${TESTSUITE}"
+run_cmd="dovetail run --openrc ${OPENRC} --testsuite ${TESTSUITE} -d"
+echo "Container exec command: ${list_cmd}"
+docker exec $container_id ${list_cmd}
+echo "Container exec command: ${run_cmd}"
+docker exec $container_id ${run_cmd}
+
+sudo cp -r ${DOVETAIL_REPO_DIR}/results ./
+# To make sure the file owner is the current user, for the copied results files in the above line
+# if not, there will be error when next time to wipe workspace
+# CURRENT_USER=${SUDO_USER:-$USER}
+# PRIMARY_GROUP=$(id -gn $CURRENT_USER)
+# sudo chown -R ${CURRENT_USER}:${PRIMARY_GROUP} ${WORKSPACE}/results
 
 echo "Dovetail: done!"
+
diff --git a/jjb/dovetail/dovetail-weekly-jobs.yml b/jjb/dovetail/dovetail-weekly-jobs.yml
new file mode 100644 (file)
index 0000000..915feb5
--- /dev/null
@@ -0,0 +1,135 @@
+- project:
+    name: dovetail-weekly-jobs
+    project: dovetail
+#--------------------------------
+# BRANCH ANCHORS
+#--------------------------------
+    master: &master
+        stream: master
+        branch: '{stream}'
+        dovetail-branch: '{stream}'
+        gs-pathname: ''
+        docker-tag: 'latest'
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        dovetail-branch: master
+        gs-pathname: '/{stream}'
+        docker-tag: 'latest'
+
+#--------------------------------
+# POD, INSTALLER, AND BRANCH MAPPING
+#--------------------------------
+#    Installers using labels
+#            CI PODs
+# This section should only contain the installers
+# that have been switched using labels for slaves
+#--------------------------------
+    pod:
+#        - baremetal:
+#            slave-label: apex-baremetal
+#            SUT: apex
+#            <<: *danube
+        - baremetal:
+            slave-label: compass-baremetal
+            SUT: compass
+            <<: *danube
+#        - baremetal:
+#            slave-label: fuel-baremetal
+#            SUT: fuel
+#            <<: *danube
+#        - baremetal:
+#            slave-label: joid-baremetal
+#            SUT: joid
+#            <<: *danube
+
+    testsuite:
+        - 'debug'
+        - 'compliance_set'
+
+    loop:
+        - 'weekly':
+            job-timeout: 180
+
+    jobs:
+        - 'dovetail-{SUT}-{pod}-{testsuite}-{loop}-{stream}'
+
+################################
+# job template
+################################
+- job-template:
+    name: 'dovetail-{SUT}-{pod}-{testsuite}-{loop}-{stream}'
+
+    disabled: true
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-per-node: 1
+            option: 'project'
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER Scenario: $DEPLOY_SCENARIO'
+        - timeout:
+            timeout: '{job-timeout}'
+            abort: true
+        - fix-workspace-permissions
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{dovetail-branch}'
+        - '{SUT}-defaults'
+        - '{slave-label}-defaults'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-nosdn-nofeature-ha'
+        - string:
+            name: DOCKER_TAG
+            default: '{docker-tag}'
+            description: 'Tag to pull dovetail docker image'
+        - string:
+            name: CI_DEBUG
+            default: 'true'
+            description: "Show debug output information"
+        - string:
+            name: TESTSUITE
+            default: '{testsuite}'
+            description: "dovetail testsuite to run"
+        - string:
+            name: DOVETAIL_REPO_DIR
+            default: "/home/opnfv/dovetail"
+            description: "Directory where the dovetail repository is cloned"
+
+    scm:
+        - git-scm
+
+    builders:
+        - description-setter:
+            description: "POD: $NODE_NAME"
+        - 'dovetail-cleanup'
+        - 'dovetail-run'
+
+    publishers:
+        - archive:
+            artifacts: 'results/**/*'
+            allow-empty: true
+            fingerprint: true
+
+########################
+# builder macros
+########################
+- builder:
+    name: dovetail-run-weekly
+    builders:
+        - shell:
+            !include-raw: ./dovetail-run.sh
+- builder:
+    name: dovetail-cleanup-weekly
+    builders:
+        - shell:
+            !include-raw: ./dovetail-cleanup.sh
index c660af5..63eb044 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
similarity index 75%
rename from jjb/daisy4nfv/daisy4nfv-virtual-deploy.sh
rename to jjb/escalator/escalator-basic.sh
index 8936be6..9c739c4 100755 (executable)
@@ -1,6 +1,5 @@
 #!/bin/bash
-
 echo "--------------------------------------------------------"
-echo "This is diasy4nfv virtual deploy job!"
+echo "This is escalator basic job!"
 echo "--------------------------------------------------------"
 
diff --git a/jjb/escalator/escalator-build.sh b/jjb/escalator/escalator-build.sh
new file mode 100755 (executable)
index 0000000..0e35c27
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+echo "--------------------------------------------------------"
+echo "This is escalator build job!"
+echo "--------------------------------------------------------"
+
+# set OPNFV_ARTIFACT_VERSION
+if [[ "$JOB_NAME" =~ "merge" ]]; then
+    echo "Building Escalator TAR for a merged change"
+    export OPNFV_ARTIFACT_VERSION="gerrit-$GERRIT_CHANGE_NUMBER"
+else
+    export OPNFV_ARTIFACT_VERSION=$(date -u +"%Y-%m-%d_%H-%M-%S")
+fi
+
+# build output directory
+OUTPUT_DIR=$WORKSPACE/build_output
+mkdir -p $OUTPUT_DIR
+
+# start the build
+cd $WORKSPACE
+./ci/build.sh $OUTPUT_DIR $OPNFV_ARTIFACT_VERSION
+
+# save information regarding artifact into file
+(
+    echo "OPNFV_ARTIFACT_VERSION=$OPNFV_ARTIFACT_VERSION"
+    echo "OPNFV_GIT_URL=$(git config --get remote.origin.url)"
+    echo "OPNFV_GIT_SHA1=$(git rev-parse HEAD)"
+    echo "OPNFV_ARTIFACT_URL=$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.bin"
+    echo "OPNFV_BUILD_URL=$BUILD_URL"
+) > $WORKSPACE/opnfv.properties
+
+echo
+echo "--------------------------------------------------------"
+echo "Done!"
diff --git a/jjb/escalator/escalator-upload-artifact.sh b/jjb/escalator/escalator-upload-artifact.sh
new file mode 100755 (executable)
index 0000000..781fb3e
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/bash
+echo "--------------------------------------------------------"
+echo "This is escalator upload job!"
+echo "--------------------------------------------------------"
+
+set -o pipefail
+
+# check if we built something
+if [ -f $WORKSPACE/.noupload ]; then
+    echo "Nothing new to upload. Exiting."
+    /bin/rm -f $WORKSPACE/.noupload
+    exit 0
+fi
+
+# source the opnfv.properties to get ARTIFACT_VERSION
+source $WORKSPACE/opnfv.properties
+
+importkey () {
+# clone releng repository
+echo "Cloning releng repository..."
+[ -d releng ] && rm -rf releng
+git clone https://gerrit.opnfv.org/gerrit/releng $WORKSPACE/releng/ &> /dev/null
+#this is where we import the siging key
+if [ -f $WORKSPACE/releng/utils/gpg_import_key.sh ]; then
+  source $WORKSPACE/releng/utils/gpg_import_key.sh
+fi
+}
+
+signtar () {
+gpg2 -vvv --batch --yes --no-tty \
+  --default-key opnfv-helpdesk@rt.linuxfoundation.org  \
+  --passphrase besteffort \
+  --detach-sig $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz
+
+gsutil cp $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz.sig gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz.sig
+echo "TAR signature Upload Complete!"
+}
+
+uploadtar () {
+# log info to console
+echo "Uploading $INSTALLER_TYPE artifact. This could take some time..."
+echo
+
+cd $WORKSPACE
+# upload artifact and additional files to google storage
+gsutil cp $BUILD_DIRECTORY/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz > gsutil.tar.log 2>&1
+gsutil cp $WORKSPACE/opnfv.properties \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.properties > gsutil.properties.log 2>&1
+if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
+    gsutil cp $WORKSPACE/opnfv.properties \
+    gs://$GS_URL/latest.properties > gsutil.latest.log 2>&1
+elif [[ "$JOB_NAME" =~ "merge" ]]; then
+    echo "Uploaded Escalator TAR for a merged change"
+fi
+
+gsutil -m setmeta \
+    -h "Content-Type:text/html" \
+    -h "Cache-Control:private, max-age=0, no-transform" \
+    gs://$GS_URL/latest.properties \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.properties > /dev/null 2>&1
+
+gsutil -m setmeta \
+    -h "Cache-Control:private, max-age=0, no-transform" \
+    gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz > /dev/null 2>&1
+
+# disabled errexit due to gsutil setmeta complaints
+#   BadRequestException: 400 Invalid argument
+# check if we uploaded the file successfully to see if things are fine
+gsutil ls gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz > /dev/null 2>&1
+if [[ $? -ne 0 ]]; then
+    echo "Problem while uploading artifact!"
+    echo "Check log $WORKSPACE/gsutil.bin.log on the machine where this build is done."
+    exit 1
+fi
+
+echo "Done!"
+echo
+echo "--------------------------------------------------------"
+echo
+echo "Artifact is available as http://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.tar.gz"
+echo
+echo "--------------------------------------------------------"
+echo
+}
+
+importkey
+signtar
+uploadtar
similarity index 54%
rename from jjb/fuel/fuel-verify-jobs-experimental.yml
rename to jjb/escalator/escalator.yml
index ae83b08..041a41f 100644 (file)
@@ -1,78 +1,62 @@
 - project:
-    # TODO: rename the project name
-    # TODO: get rid of appended -exp from the remainder of the file
-    name: 'fuel-verify-jobs-experimental'
+    name: 'escalator'
 
-    project: 'fuel'
-
-    installer: 'fuel'
-#------------------------------------
+    project: 'escalator'
+#####################################
 # branch definitions
-#------------------------------------
-    # TODO: enable master once things settle
-    stream-exp:
-        - experimental:
-            branch: 'stable/{stream-exp}'
-            gs-pathname: '/{stream-exp}'
+#####################################
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
             disabled: false
-#------------------------------------
-# patch verification phases
-#------------------------------------
+#####################################
+# phases
+#####################################
     phase:
         - 'basic':
-            # this phase does basic commit message check, unit test and so on
-            slave-label: 'opnfv-build'
+            slave-label: 'opnfv-build-centos'
         - 'build':
-            # this phase builds artifacts if valid for given installer
-            slave-label: 'opnfv-build-ubuntu'
-        - 'deploy-virtual':
-            # this phase does virtual deployment using the artifacts produced in previous phase
-            slave-label: 'fuel-virtual'
-        - 'smoke-test':
-            # this phase runs functest smoke test
-            slave-label: 'fuel-virtual'
-#------------------------------------
+            slave-label: 'opnfv-build-centos'
+#####################################
 # jobs
-#------------------------------------
+#####################################
     jobs:
-        - 'fuel-verify-{stream-exp}'
-        - 'fuel-verify-{phase}-{stream-exp}'
-#------------------------------------
+        - 'escalator-verify-{stream}'
+        - 'escalator-verify-{phase}-{stream}'
+        - 'escalator-merge-{stream}'
+        - 'escalator-merge-{phase}-{stream}'
+#####################################
 # job templates
-#------------------------------------
+#####################################
 - job-template:
-    name: 'fuel-verify-{stream-exp}'
+    name: 'escalator-verify-{stream}'
 
     project-type: multijob
 
-    disabled: '{obj:disabled}'
+    disabled: false
 
-    # TODO: this is valid for experimental only
-    #       enable concurrency for master once things settle
-    concurrent: false
+    concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             option: 'project'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
-                file-paths:
-                  - compare-type: ANT
-                    pattern: 'ci/**'
-                  - compare-type: ANT
-                    pattern: 'build/**'
-                  - compare-type: ANT
-                    pattern: 'deploy/**'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
-                    pattern: 'docs/**'
+                    pattern: 'docs/**|.gitignore'
             readable-message: true
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-defaults'
-        - 'fuel-verify-defaults-exp':
+        - 'escalator-defaults':
             gs-pathname: '{gs-pathname}'
 
     builders:
             name: basic
             condition: SUCCESSFUL
             projects:
-                - name: 'fuel-verify-basic-{stream-exp}'
+                - name: 'escalator-verify-basic-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
             name: build
             condition: SUCCESSFUL
             projects:
-                - name: 'fuel-verify-build-{stream-exp}'
+                - name: 'escalator-verify-build-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                   node-parameters: false
                   kill-phase-on: FAILURE
                   abort-all-job: true
+
+- job-template:
+    name: 'escalator-verify-{phase}-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    scm:
+        - git-scm-gerrit
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{slave-label}-defaults'
+        - 'escalator-defaults':
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - '{project}-verify-{phase}-macro'
+
+- job-template:
+    name: 'escalator-merge-{stream}'
+
+    project-type: multijob
+
+    disabled: false
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            option: 'project'
+
+    scm:
+        - git-scm-gerrit
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - change-merged-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'remerge'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**|.gitignore'
+            readable-message: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-defaults'
+        - 'escalator-defaults':
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
         - multijob:
-            name: deploy-virtual
+            name: basic
             condition: SUCCESSFUL
             projects:
-                - name: 'fuel-verify-deploy-virtual-{stream-exp}'
+                - name: 'escalator-merge-basic-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                   kill-phase-on: FAILURE
                   abort-all-job: true
         - multijob:
-            name: smoke-test
+            name: build
             condition: SUCCESSFUL
             projects:
-                - name: 'fuel-verify-smoke-test-{stream-exp}'
+                - name: 'escalator-merge-build-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                   abort-all-job: true
 
 - job-template:
-    name: 'fuel-verify-{phase}-{stream-exp}'
+    name: 'escalator-merge-{phase}-{stream}'
 
     disabled: '{obj:disabled}'
 
     concurrent: true
 
-    properties:
-        - throttle:
-            enabled: true
-            max-total: 6
-            option: 'project'
-        - build-blocker:
-            use-build-blocker: true
-            blocking-jobs:
-                - 'fuel-verify-deploy-.*'
-                - 'fuel-verify-test-.*'
-            block-level: 'NODE'
-
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
+
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
-        - '{installer}-defaults'
-        - 'fuel-verify-defaults-exp':
+        - 'escalator-defaults':
             gs-pathname: '{gs-pathname}'
 
     builders:
         - description-setter:
             description: "Built on $NODE_NAME"
-        - '{project}-verify-{phase}-macro-exp'
-#------------------------------------
+        - '{project}-merge-{phase}-macro'
+#####################################
 # builder macros
-#------------------------------------
+#####################################
 - builder:
-    name: 'fuel-verify-basic-macro-exp'
+    name: 'escalator-verify-basic-macro'
     builders:
         - shell:
-            !include-raw: ./fuel-basic-exp.sh
+            !include-raw: ./escalator-basic.sh
 
 - builder:
-    name: 'fuel-verify-build-macro-exp'
+    name: 'escalator-verify-build-macro'
     builders:
         - shell:
-            !include-raw: ./fuel-build-exp.sh
-        - shell:
-            !include-raw: ./fuel-workspace-cleanup.sh
+            !include-raw: ./escalator-build.sh
 
 - builder:
-    name: 'fuel-verify-deploy-virtual-macro-exp'
+    name: 'escalator-merge-basic-macro'
     builders:
         - shell:
-            !include-raw: ./fuel-deploy-exp.sh
+            !include-raw: ./escalator-basic.sh
 
 - builder:
-    name: 'fuel-verify-smoke-test-macro-exp'
+    name: 'escalator-merge-build-macro'
     builders:
         - shell:
-            !include-raw: ./fuel-smoke-test-exp.sh
-#------------------------------------
+            !include-raw:
+                - ./escalator-build.sh
+                - ./escalator-upload-artifact.sh
+#####################################
 # parameter macros
-#------------------------------------
+#####################################
 - parameter:
-    name: 'fuel-verify-defaults-exp'
+    name: 'escalator-defaults'
     parameters:
         - string:
             name: BUILD_DIRECTORY
diff --git a/jjb/fuel/fuel-basic-exp.sh b/jjb/fuel/fuel-basic-exp.sh
deleted file mode 100755 (executable)
index a70a0c7..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-set -o nounset
-
-echo "-----------------------------------------------------------------------"
-echo $GERRIT_CHANGE_COMMIT_MESSAGE
-echo "-----------------------------------------------------------------------"
-
-# proposal for specifying the scenario name in commit message
-# currently only 1 scenario name is supported but depending on
-# the need, it can be expanded, supporting multiple scenarios
-# using comma separated list or something
-SCENARIO_NAME_PATTERN="(?<=@scenario:).*?(?=@)"
-SCENARIO_NAME=(echo $GERRIT_CHANGE_COMMIT_MESSAGE | grep -oP "$SCENARIO_NAME_PATTERN")
-if [[ $? -ne 0 ]]; then
-    echo "The patch verification will be done only with build!"
-else
-    echo "Will run full verification; build, deploy, and smoke test using scenario $SCENARIO_NAME"
-fi
diff --git a/jjb/fuel/fuel-build-exp.sh b/jjb/fuel/fuel-build-exp.sh
deleted file mode 100755 (executable)
index f7f613d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-if [[ "$JOB_NAME" =~ (verify|merge|daily|weekly) ]]; then
-    JOB_TYPE=${BASH_REMATCH[0]}
-else
-    echo "Unable to determine job type!"
-    exit 1
-fi
-
-echo "Not activated!"
index c66dc3d..e1a4c02 100755 (executable)
@@ -25,8 +25,10 @@ if [[ "$JOB_NAME" =~ "daily" ]]; then
     echo "Checking to see if we already built and stored Fuel ISO for this commit"
 
     curl -s -o $LATEST_ISO_PROPERTIES http://$GS_URL/latest.properties 2>/dev/null
+fi
 
-    # get metadata of latest ISO
+# get metadata of latest ISO
+if grep -q OPNFV_GIT_SHA1 $LATEST_ISO_PROPERTIES 2>/dev/null; then
     LATEST_ISO_SHA1=$(grep OPNFV_GIT_SHA1 $LATEST_ISO_PROPERTIES | cut -d'=' -f2)
     LATEST_ISO_URL=$(grep OPNFV_ARTIFACT_URL $LATEST_ISO_PROPERTIES | cut -d'=' -f2)
 else
index 1c7946a..32abad6 100644 (file)
@@ -15,8 +15,8 @@
         branch: '{stream}'
         disabled: false
         gs-pathname: ''
-    colorado: &colorado
-        stream: colorado
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         disabled: false
         gs-pathname: '/{stream}'
             <<: *master
         - baremetal:
             slave-label: fuel-baremetal
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: fuel-virtual
-            <<: *colorado
+            <<: *danube
 #--------------------------------
 #        None-CI PODs
 #--------------------------------
             <<: *master
         - zte-pod1:
             slave-label: zte-pod1
-            <<: *colorado
+            <<: *danube
         - zte-pod3:
             slave-label: zte-pod3
-            <<: *colorado
+            <<: *danube
 #--------------------------------
 #       scenarios
 #--------------------------------
             auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
         - 'os-nosdn-kvm_ovs-ha':
             auto-trigger-name: 'daily-trigger-disabled'
+        - 'os-nosdn-kvm_ovs_dpdk-ha':
+            auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
+        - 'os-nosdn-kvm_ovs_dpdk_bar-ha':
+            auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
         # NOHA scenarios
         - 'os-nosdn-nofeature-noha':
             auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
             auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
         - 'os-nosdn-ovs-noha':
             auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
+        - 'os-nosdn-kvm_ovs_dpdk-noha':
+            auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
+        - 'os-nosdn-kvm_ovs_dpdk_bar-noha':
+            auto-trigger-name: 'fuel-{scenario}-{pod}-daily-{stream}-trigger'
 
     jobs:
         - 'fuel-{scenario}-{pod}-daily-{stream}'
     concurrent: false
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             use-build-blocker: true
             blocking-jobs:
                 - 'fuel-os-.*?-{pod}-daily-.*'
+                - 'fuel-os-.*?-{pod}-weekly-.*'
             block-level: 'NODE'
 
     wrappers:
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults':
             installer: '{installer}'
 
     builders:
         - description-setter:
-            description: "POD: $NODE_NAME"
+            description: "Built on $NODE_NAME"
         - trigger-builds:
             - project: 'fuel-deploy-{pod}-daily-{stream}'
               current-parameters: false
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             blocking-jobs:
                 - 'fuel-deploy-{pod}-daily-.*'
                 - 'fuel-deploy-generic-daily-.*'
+                - 'fuel-deploy-{pod}-weekly-.*'
+                - 'fuel-deploy-generic-weekly-.*'
             block-level: 'NODE'
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults':
             installer: '{installer}'
             description: 'Deployment timeout in minutes'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     wrappers:
         - build-name:
 
     builders:
         - description-setter:
-            description: "POD: $NODE_NAME"
+            description: "Built on $NODE_NAME"
         - shell:
             !include-raw-escape: ./fuel-download-artifact.sh
         - shell:
 
     publishers:
         - email:
-            recipients: jonas.bjurel@ericsson.com stefan.k.berg@ericsson.com peter.barabas@ericsson.com fzhadaev@mirantis.com
+            recipients: peter.barabas@ericsson.com fzhadaev@mirantis.com
 
 ########################
 # parameter macros
     triggers:
         - timed: '5 2 * * *'
 - trigger:
-    name: 'fuel-os-onos-sfc-ha-baremetal-daily-master-trigger'
+    name: 'fuel-os-nosdn-ovs-ha-baremetal-daily-master-trigger'
     triggers:
         - timed: '5 5 * * *'
+- trigger:
+    name: 'fuel-os-onos-sfc-ha-baremetal-daily-master-trigger'
+    triggers:
+        - timed: '' # '5 5 * * *'
 - trigger:
     name: 'fuel-os-onos-nofeature-ha-baremetal-daily-master-trigger'
     triggers:
-        - timed: '5 8 * * *'
+        - timed: '' # '5 8 * * *'
 - trigger:
     name: 'fuel-os-odl_l2-sfc-ha-baremetal-daily-master-trigger'
     triggers:
     triggers:
         - timed: '5 17 * * *'
 - trigger:
-    name: 'fuel-os-nosdn-ovs-ha-baremetal-daily-master-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-baremetal-daily-master-trigger'
     triggers:
-        - timed: '5 20 * * *'
-
+        - timed: '30 12 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-baremetal-daily-master-trigger'
+    triggers:
+        - timed: '30 8 * * *'
 # NOHA Scenarios
 - trigger:
     name: 'fuel-os-nosdn-nofeature-noha-baremetal-daily-master-trigger'
     name: 'fuel-os-nosdn-ovs-noha-baremetal-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-baremetal-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-baremetal-daily-master-trigger'
+    triggers:
+        - timed: ''
 #-----------------------------------------------
-# Triggers for job running on fuel-baremetal against colorado branch
+# Triggers for job running on fuel-baremetal against danube branch
 #-----------------------------------------------
 # HA Scenarios
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 20 * * *'
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 23 * * *'
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 2 * * *'
 - trigger:
-    name: 'fuel-os-onos-sfc-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-ha-baremetal-daily-danube-trigger'
     triggers:
-        - timed: '0 5 * * *'
+        - timed: '' # '0 5 * * *'
 - trigger:
-    name: 'fuel-os-onos-nofeature-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-ha-baremetal-daily-danube-trigger'
     triggers:
-        - timed: '0 8 * * *'
+        - timed: '' # '0 8 * * *'
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 11 * * *'
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 14 * * *'
 - trigger:
-    name: 'fuel-os-nosdn-kvm-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 17 * * *'
 - trigger:
-    name: 'fuel-os-nosdn-ovs-ha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-ha-baremetal-daily-danube-trigger'
     triggers:
         - timed: '0 20 * * *'
-
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-baremetal-daily-danube-trigger'
+    triggers:
+        - timed: '0 12 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-baremetal-daily-danube-trigger'
+    triggers:
+        - timed: '0 8 * * *'
 # NOHA Scenarios
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-noha-baremetal-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-noha-baremetal-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-baremetal-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-baremetal-daily-danube-trigger'
     triggers:
         - timed: ''
 #-----------------------------------------------
     name: 'fuel-os-nosdn-ovs-ha-virtual-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-virtual-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-virtual-daily-master-trigger'
+    triggers:
+        - timed: ''
 # NOHA Scenarios
 - trigger:
     name: 'fuel-os-nosdn-nofeature-noha-virtual-daily-master-trigger'
 - trigger:
     name: 'fuel-os-onos-sfc-noha-virtual-daily-master-trigger'
     triggers:
-        - timed: '35 20 * * *'
+        - timed: '' # '35 20 * * *'
 - trigger:
     name: 'fuel-os-onos-nofeature-noha-virtual-daily-master-trigger'
     triggers:
-        - timed: '5 23 * * *'
+        - timed: '' # '5 23 * * *'
 - trigger:
     name: 'fuel-os-odl_l2-sfc-noha-virtual-daily-master-trigger'
     triggers:
     name: 'fuel-os-nosdn-ovs-noha-virtual-daily-master-trigger'
     triggers:
         - timed: '5 9 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-virtual-daily-master-trigger'
+    triggers:
+        - timed: '30 16 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-virtual-daily-master-trigger'
+    triggers:
+        - timed: '30 20 * * *'
 #-----------------------------------------------
-# Triggers for job running on fuel-virtual against colorado branch
+# Triggers for job running on fuel-virtual against danube branch
 #-----------------------------------------------
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-virtual-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-ha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-virtual-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-virtual-daily-danube-trigger'
     triggers:
         - timed: ''
 # NOHA Scenarios
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '0 13 * * *'
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '30 15 * * *'
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '0 18 * * *'
 - trigger:
-    name: 'fuel-os-onos-sfc-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-noha-virtual-daily-danube-trigger'
     triggers:
-        - timed: '30 20 * * *'
+        - timed: '' # '30 20 * * *'
 - trigger:
-    name: 'fuel-os-onos-nofeature-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-noha-virtual-daily-danube-trigger'
     triggers:
-        - timed: '0 23 * * *'
+        - timed: '' # '0 23 * * *'
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '30 1 * * *'
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '0 4 * * *'
 - trigger:
-    name: 'fuel-os-nosdn-kvm-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '30 6 * * *'
 - trigger:
-    name: 'fuel-os-nosdn-ovs-noha-virtual-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-noha-virtual-daily-danube-trigger'
     triggers:
         - timed: '0 9 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-virtual-daily-danube-trigger'
+    triggers:
+        - timed: '0 16 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-virtual-daily-danube-trigger'
+    triggers:
+        - timed: '0 20 * * *'
 #-----------------------------------------------
 # ZTE POD1 Triggers running against master branch
 #-----------------------------------------------
     name: 'fuel-os-nosdn-ovs-ha-zte-pod1-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-zte-pod1-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-zte-pod1-daily-master-trigger'
+    triggers:
+        - timed: ''
 # NOHA Scenarios
 - trigger:
     name: 'fuel-os-nosdn-nofeature-noha-zte-pod1-daily-master-trigger'
     name: 'fuel-os-nosdn-ovs-noha-zte-pod1-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-zte-pod1-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-zte-pod1-daily-master-trigger'
+    triggers:
+        - timed: ''
 
 #-----------------------------------------------
 # ZTE POD2 Triggers running against master branch
 - trigger:
     name: 'fuel-os-odl_l2-nofeature-ha-zte-pod2-daily-master-trigger'
     triggers:
-        - timed: '0 18 * * *'
+        - timed: ''
 - trigger:
     name: 'fuel-os-odl_l3-nofeature-ha-zte-pod2-daily-master-trigger'
     triggers:
     name: 'fuel-os-nosdn-ovs-ha-zte-pod2-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-zte-pod2-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-zte-pod2-daily-master-trigger'
+    triggers:
+        - timed: ''
 # NOHA Scenarios
 - trigger:
     name: 'fuel-os-nosdn-nofeature-noha-zte-pod2-daily-master-trigger'
     name: 'fuel-os-nosdn-ovs-noha-zte-pod2-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-zte-pod2-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-zte-pod2-daily-master-trigger'
+    triggers:
+        - timed: ''
 #-----------------------------------------------
 # ZTE POD3 Triggers running against master branch
 #-----------------------------------------------
     name: 'fuel-os-nosdn-ovs-ha-zte-pod3-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-zte-pod3-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-zte-pod3-daily-master-trigger'
+    triggers:
+        - timed: ''
 # NOHA Scenarios
 - trigger:
     name: 'fuel-os-nosdn-nofeature-noha-zte-pod3-daily-master-trigger'
     name: 'fuel-os-nosdn-ovs-noha-zte-pod3-daily-master-trigger'
     triggers:
         - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-zte-pod3-daily-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-zte-pod3-daily-master-trigger'
+    triggers:
+        - timed: ''
 #-----------------------------------------------
-# ZTE POD1 Triggers running against colorado branch
+# ZTE POD1 Triggers running against danube branch
 #-----------------------------------------------
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: '0 2 * * *'
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-ha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-ha-zte-pod1-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-zte-pod1-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 # NOHA Scenarios
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-noha-zte-pod1-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-noha-zte-pod1-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-zte-pod1-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-zte-pod1-daily-danube-trigger'
     triggers:
         - timed: ''
 
 #-----------------------------------------------
-# ZTE POD2 Triggers running against colorado branch
+# ZTE POD2 Triggers running against danube branch
 #-----------------------------------------------
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-nofeature-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-ha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-ha-zte-pod2-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-zte-pod2-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 # NOHA Scenarios
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-noha-zte-pod2-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-noha-zte-pod2-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-zte-pod2-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-zte-pod2-daily-danube-trigger'
     triggers:
         - timed: ''
 #-----------------------------------------------
-# ZTE POD3 Triggers running against colorado branch
+# ZTE POD3 Triggers running against danube branch
 #-----------------------------------------------
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-ha-zte-pod3-daily-danube-trigger'
+    triggers:
+        - timed: '0 18 * * *'
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-odl_l3-nofeature-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-ha-zte-pod3-daily-danube-trigger'
+    triggers:
+        - timed: '0 2 * * *'
+- trigger:
+    name: 'fuel-os-nosdn-ovs-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-ha-zte-pod3-daily-danube-trigger'
     triggers:
-        - timed: '0 18 * * *'
+        - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-ha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-ha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 # NOHA Scenarios
 - trigger:
-    name: 'fuel-os-nosdn-nofeature-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-nofeature-noha-zte-pod3-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l2-nofeature-noha-zte-pod3-daily-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'fuel-os-odl_l3-nofeature-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-nofeature-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-onos-sfc-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l3-nofeature-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-onos-nofeature-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-sfc-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-sfc-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-onos-nofeature-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-odl_l2-bgpvpn-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-sfc-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-odl_l2-bgpvpn-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-ovs-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-kvm-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'fuel-os-nosdn-ovs-noha-zte-pod3-daily-colorado-trigger'
+    name: 'fuel-os-nosdn-kvm_ovs_dpdk_bar-noha-zte-pod3-daily-danube-trigger'
     triggers:
         - timed: ''
diff --git a/jjb/fuel/fuel-deploy-exp.sh b/jjb/fuel/fuel-deploy-exp.sh
deleted file mode 100755 (executable)
index f7f613d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-if [[ "$JOB_NAME" =~ (verify|merge|daily|weekly) ]]; then
-    JOB_TYPE=${BASH_REMATCH[0]}
-else
-    echo "Unable to determine job type!"
-    exit 1
-fi
-
-echo "Not activated!"
index 48b1dac..f5bbd18 100755 (executable)
@@ -57,9 +57,9 @@ chmod a+x $TMPDIR
 
 # clone the securedlab repo
 cd $WORKSPACE
-echo "Cloning securedlab repo ${GIT_BRANCH##origin/}"
+echo "Cloning securedlab repo $BRANCH"
 git clone ssh://jenkins-ericsson@gerrit.opnfv.org:29418/securedlab --quiet \
-    --branch ${GIT_BRANCH##origin/}
+    --branch $BRANCH
 
 # log file name
 FUEL_LOG_FILENAME="${JOB_NAME}_${BUILD_NUMBER}.log.tar.gz"
@@ -95,7 +95,7 @@ echo "Deployment is done!"
 
 # upload logs for baremetal deployments
 # work with virtual deployments is still going on so we skip that for the timebeing
-if [[ "$JOB_NAME" =~ "baremetal-daily" ]]; then
+if [[ "$JOB_NAME" =~ (baremetal-daily|baremetal-weekly) ]]; then
     echo "Uploading deployment logs"
     gsutil cp $WORKSPACE/$FUEL_LOG_FILENAME gs://$GS_URL/logs/$FUEL_LOG_FILENAME > /dev/null 2>&1
     echo "Logs are available as http://$GS_URL/logs/$FUEL_LOG_FILENAME"
index 5685444..8cc552e 100755 (executable)
@@ -11,23 +11,23 @@ set -o errexit
 set -o pipefail
 
 # use proxy url to replace the nomral URL, for googleusercontent.com will be blocked randomly
-[[ "$NODE_NAME" =~ (zte) ]] && GS_URL=$GS_BASE_PROXY
+[[ "$NODE_NAME" =~ (zte) ]] && GS_URL=${GS_BASE_PROXY%%/*}/$GS_URL
 
 if [[ "$JOB_NAME" =~ "merge" ]]; then
     echo "Downloading http://$GS_URL/opnfv-gerrit-$GERRIT_CHANGE_NUMBER.properties"
     # get the properties file for the Fuel ISO built for a merged change
-    curl -s -o $WORKSPACE/latest.properties http://$GS_URL/opnfv-gerrit-$GERRIT_CHANGE_NUMBER.properties
+    curl -L -s -o $WORKSPACE/latest.properties http://$GS_URL/opnfv-gerrit-$GERRIT_CHANGE_NUMBER.properties
 else
     # get the latest.properties file in order to get info regarding latest artifact
     echo "Downloading http://$GS_URL/latest.properties"
-    curl -s -o $WORKSPACE/latest.properties http://$GS_URL/latest.properties
+    curl -L -s -o $WORKSPACE/latest.properties http://$GS_URL/latest.properties
 fi
 
 # check if we got the file
-[[ -f latest.properties ]] || exit 1
+[[ -f $WORKSPACE/latest.properties ]] || exit 1
 
 # source the file so we get artifact metadata
-source latest.properties
+source $WORKSPACE/latest.properties
 
 # echo the info about artifact that is used during the deployment
 OPNFV_ARTIFACT=${OPNFV_ARTIFACT_URL/*\/}
@@ -36,7 +36,7 @@ echo "Using $OPNFV_ARTIFACT for deployment"
 # using ISOs for verify & merge jobs from local storage will be enabled later
 if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
     # check if we already have the ISO to avoid redownload
-    ISOSTORE="/iso_mount/opnfv_ci/${GIT_BRANCH##*/}"
+    ISOSTORE="/iso_mount/opnfv_ci/${BRANCH##*/}"
     if [[ -f "$ISOSTORE/$OPNFV_ARTIFACT" ]]; then
         echo "ISO exists locally. Skipping the download and using the file from ISO store"
         ln -s $ISOSTORE/$OPNFV_ARTIFACT $WORKSPACE/opnfv.iso
@@ -59,7 +59,7 @@ echo "--------------------------------------------------------"
 echo
 
 # download the file
-curl -s -o $WORKSPACE/opnfv.iso http://$OPNFV_ARTIFACT_URL > gsutil.iso.log 2>&1
+curl -L -s -o $WORKSPACE/opnfv.iso http://$OPNFV_ARTIFACT_URL > gsutil.iso.log 2>&1
 
 # list the file
 ls -al $WORKSPACE/opnfv.iso
diff --git a/jjb/fuel/fuel-plugin-build.sh b/jjb/fuel/fuel-plugin-build.sh
deleted file mode 100755 (executable)
index f7f613d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-if [[ "$JOB_NAME" =~ (verify|merge|daily|weekly) ]]; then
-    JOB_TYPE=${BASH_REMATCH[0]}
-else
-    echo "Unable to determine job type!"
-    exit 1
-fi
-
-echo "Not activated!"
diff --git a/jjb/fuel/fuel-plugin-test.sh b/jjb/fuel/fuel-plugin-test.sh
deleted file mode 100755 (executable)
index f7f613d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-if [[ "$JOB_NAME" =~ (verify|merge|daily|weekly) ]]; then
-    JOB_TYPE=${BASH_REMATCH[0]}
-else
-    echo "Unable to determine job type!"
-    exit 1
-fi
-
-echo "Not activated!"
diff --git a/jjb/fuel/fuel-plugin-verify-jobs.yml b/jjb/fuel/fuel-plugin-verify-jobs.yml
deleted file mode 100644 (file)
index 4fea26b..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-- project:
-    name: 'fuel-plugin-verify-jobs'
-
-    project: 'fuel-plugin'
-
-    installer: 'fuel'
-#####################################
-# branch definitions
-#####################################
-    stream:
-        - master:
-            upstream-branch: '{stream}'
-            opnfv-branch: 'experimental'
-            gs-pathname: ''
-            disabled: false
-#####################################
-# patch verification phases
-#####################################
-    phase:
-        - 'build':
-            slave-label: 'opnfv-build-ubuntu'
-        - 'test':
-            slave-label: 'opnfv-build-ubuntu'
-#####################################
-# jobs
-#####################################
-    jobs:
-        - 'fuel-verify-plugin-{stream}'
-        - 'fuel-verify-plugin-{phase}-{stream}'
-#####################################
-# job templates
-#####################################
-- job-template:
-    name: 'fuel-verify-plugin-{stream}'
-
-    project-type: multijob
-
-    disabled: '{obj:disabled}'
-
-    concurrent: true
-
-    properties:
-        - throttle:
-            enabled: true
-            max-total: 4
-            option: 'project'
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-        - gerrit-parameter:
-            branch: '{upstream-branch}'
-            description: 'OpenStack branch to use'
-        - string:
-            name: OPNFV_BRANCH
-            default: '{opnfv-branch}'
-            description: 'OPNFV branch to use'
-        - 'opnfv-build-defaults'
-        - 'fuel-verify-plugin-defaults':
-            gs-pathname: '{gs-pathname}'
-
-    scm:
-        - git:
-            url: 'https://git.openstack.org/$GERRIT_PROJECT'
-            refspec: '$GERRIT_REFSPEC'
-            branches:
-                - 'origin/$GERRIT_BRANCH'
-            skip-tag: true
-            choosing-strategy: 'gerrit'
-            timeout: 10
-            wipe-workspace: true
-
-    wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
-        - timeout:
-            timeout: 360
-            fail: true
-
-    triggers:
-        - gerrit:
-            server-name: 'review.openstack.org'
-            silent-start: false
-            skip-vote:
-                successful: true
-                failed: true
-                unstable: true
-                notbuilt: true
-            escape-quotes: true
-            trigger-on:
-                - patchset-created-event:
-                    exclude-drafts: 'false'
-                    exclude-trivial-rebase: 'false'
-                    exclude-no-code-change: 'false'
-                - comment-added-contains-event:
-                    comment-contains-value: 'recheck'
-                - comment-added-contains-event:
-                    comment-contains-value: 'reverify'
-            projects:
-              - project-compare-type: 'PLAIN'
-                project-pattern: 'openstack/fuel-plugin-bgpvpn'
-                branches:
-                  - branch-compare-type: 'ANT'
-                    branch-pattern: '**/{upstream-branch}'
-                forbidden-file-paths:
-                  - compare-type: ANT
-                    pattern: 'README.md|.gitignore|.gitreview'
-              - project-compare-type: 'PLAIN'
-                project-pattern: 'openstack/fuel-plugin-onos'
-                branches:
-                  - branch-compare-type: 'ANT'
-                    branch-pattern: '**/{upstream-branch}'
-                forbidden-file-paths:
-                  - compare-type: ANT
-                    pattern: 'README.md|.gitignore|.gitreview'
-            readable-message: true
-
-    builders:
-        - description-setter:
-            description: "Built on $NODE_NAME"
-        - multijob:
-            name: build
-            condition: SUCCESSFUL
-            projects:
-                - name: 'fuel-verify-plugin-build-{stream}'
-                  current-parameters: false
-                  predefined-parameters: |
-                    GERRIT_PROJECT=$GERRIT_PROJECT
-                    GERRIT_BRANCH=$GERRIT_BRANCH
-                    GERRIT_REFSPEC=$GERRIT_REFSPEC
-                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
-                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
-                  node-parameters: false
-                  kill-phase-on: FAILURE
-                  abort-all-job: true
-        - multijob:
-            name: test
-            condition: SUCCESSFUL
-            projects:
-                - name: 'fuel-verify-plugin-test-{stream}'
-                  current-parameters: false
-                  predefined-parameters: |
-                    GERRIT_PROJECT=$GERRIT_PROJECT
-                    GERRIT_BRANCH=$GERRIT_BRANCH
-                    GERRIT_REFSPEC=$GERRIT_REFSPEC
-                    GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
-                    GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
-                  node-parameters: false
-                  kill-phase-on: FAILURE
-                  abort-all-job: true
-
-- job-template:
-    name: 'fuel-verify-plugin-{phase}-{stream}'
-
-    disabled: '{obj:disabled}'
-
-    concurrent: true
-
-    properties:
-        - throttle:
-            enabled: true
-            max-total: 6
-            option: 'project'
-        - build-blocker:
-            use-build-blocker: true
-            blocking-jobs:
-                - 'fuel-verify-plugin-test-.*'
-            block-level: 'NODE'
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-        - gerrit-parameter:
-            branch: '{upstream-branch}'
-            description: 'OpenStack branch to use'
-        - string:
-            name: OPNFV_BRANCH
-            default: '{opnfv-branch}'
-            description: 'OPNFV branch to use'
-        - '{slave-label}-defaults'
-        - '{installer}-defaults'
-        - 'fuel-verify-plugin-defaults':
-            gs-pathname: '{gs-pathname}'
-
-    scm:
-        - git:
-            url: 'https://git.openstack.org/$GERRIT_PROJECT'
-            refspec: '$GERRIT_REFSPEC'
-            branches:
-                - 'origin/$GERRIT_BRANCH'
-            skip-tag: true
-            choosing-strategy: 'gerrit'
-            timeout: 10
-            wipe-workspace: true
-
-    wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
-        - timeout:
-            timeout: 360
-            fail: true
-
-    builders:
-        - description-setter:
-            description: "Built on $NODE_NAME"
-        - 'fuel-verify-plugin-{phase}-macro'
-#####################################
-# builder macros
-#####################################
-- builder:
-    name: 'fuel-verify-plugin-build-macro'
-    builders:
-        - shell:
-            !include-raw: ./fuel-plugin-build.sh
-
-- builder:
-    name: 'fuel-verify-plugin-test-macro'
-    builders:
-        - shell:
-            !include-raw: ./fuel-plugin-test.sh
-#####################################
-# parameter macros
-#####################################
-- parameter:
-    name: 'fuel-verify-plugin-defaults'
-    parameters:
-        - string:
-            name: BUILD_DIRECTORY
-            default: $WORKSPACE/build_output
-            description: "Directory where the build artifact will be located upon the completion of the build."
-        - string:
-            name: CACHE_DIRECTORY
-            default: $HOME/opnfv/cache/$INSTALLER_TYPE
-            description: "Directory where the cache to be used during the build is located."
-        - string:
-            name: GS_URL
-            default: artifacts.opnfv.org/$PROJECT{gs-pathname}
-            description: "URL to Google Storage."
index 588ab0c..1f0ddd3 100644 (file)
@@ -13,7 +13,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
@@ -35,6 +35,7 @@
     concurrent: false
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 1
@@ -44,6 +45,7 @@
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - '{installer}-defaults'
         - choice:
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - timed: '0 H/4 * * *'
@@ -79,7 +78,7 @@
 
     publishers:
         - email:
-            recipients: jonas.bjurel@ericsson.com stefan.k.berg@ericsson.com fzhadaev@mirantis.com
+            recipients: fzhadaev@mirantis.com
 
 - job-template:
     name: 'fuel-merge-build-{stream}'
@@ -91,7 +90,6 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - '{installer}-defaults'
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
                     pattern: 'build/**'
                   - compare-type: ANT
                     pattern: 'deploy/**'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 2
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'fuel-virtual-defaults':
             installer: '{installer}'
         - fuel-project-parameter:
             gs-pathname: '{gs-pathname}'
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
                     pattern: 'build/**'
                   - compare-type: ANT
                     pattern: 'deploy/**'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
 
     publishers:
         - email:
-            recipients: jonas.bjurel@ericsson.com stefan.k.berg@ericsson.com fzhadaev@mirantis.com
+            recipients: fzhadaev@mirantis.com
 
 - job-template:
     name: 'fuel-deploy-generic-daily-{stream}'
     disabled: '{obj:disabled}'
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - string:
             name: GIT_BASE
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     wrappers:
         - build-name:
diff --git a/jjb/fuel/fuel-smoke-test-exp.sh b/jjb/fuel/fuel-smoke-test-exp.sh
deleted file mode 100755 (executable)
index f7f613d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-if [[ "$JOB_NAME" =~ (verify|merge|daily|weekly) ]]; then
-    JOB_TYPE=${BASH_REMATCH[0]}
-else
-    echo "Unable to determine job type!"
-    exit 1
-fi
-
-echo "Not activated!"
index ca4ba00..d1ac350 100755 (executable)
@@ -23,7 +23,7 @@ nfsstore () {
 # storing ISOs for verify & merge jobs will be done once we get the disk array
 if [[ ! "$JOB_NAME" =~ (verify|merge) ]]; then
     # store ISO locally on NFS first
-    ISOSTORE="/iso_mount/opnfv_ci/${GIT_BRANCH##*/}"
+    ISOSTORE="/iso_mount/opnfv_ci/${BRANCH##*/}"
     if [[ -d "$ISOSTORE" ]]; then
         # remove all but most recent 5 ISOs first to keep iso_mount clean & tidy
         cd $ISOSTORE
index f4bdbdd..549f7da 100644 (file)
@@ -12,7 +12,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
 #####################################
     phase:
         - 'basic':
-            slave-label: 'opnfv-build'
+            slave-label: 'opnfv-build-ubuntu'
         - 'build':
             slave-label: 'opnfv-build-ubuntu'
         - 'deploy-virtual':
-            slave-label: 'opnfv-build'
+            slave-label: 'opnfv-build-ubuntu'
         - 'smoke-test':
-            slave-label: 'opnfv-build'
+            slave-label: 'opnfv-build-ubuntu'
 #####################################
 # jobs
 #####################################
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             option: 'project'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -91,6 +88,7 @@
                     pattern: 'build/**'
                   - compare-type: ANT
                     pattern: 'deploy/**'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
@@ -99,9 +97,8 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
-        - 'opnfv-build-defaults'
+        - 'opnfv-build-ubuntu-defaults'
         - 'fuel-verify-defaults':
             gs-pathname: '{gs-pathname}'
 
                 - name: 'fuel-verify-basic-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'fuel-verify-build-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'fuel-verify-deploy-virtual-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'fuel-verify-smoke-test-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 6
             block-level: 'NODE'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
         - '{installer}-defaults'
diff --git a/jjb/fuel/fuel-weekly-jobs.yml b/jjb/fuel/fuel-weekly-jobs.yml
new file mode 100644 (file)
index 0000000..bd42ed8
--- /dev/null
@@ -0,0 +1,210 @@
+# jenkins job templates for Fuel
+- project:
+
+    name: fuel-weekly
+
+    project: fuel
+
+    installer: fuel
+
+#--------------------------------
+# BRANCH ANCHORS
+#--------------------------------
+    master: &master
+        stream: master
+        branch: '{stream}'
+        disabled: false
+        gs-pathname: ''
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        disabled: false
+        gs-pathname: '/{stream}'
+#--------------------------------
+# POD, INSTALLER, AND BRANCH MAPPING
+#--------------------------------
+#        CI PODs
+#--------------------------------
+    pod:
+        - baremetal:
+            slave-label: fuel-baremetal
+            <<: *master
+        - virtual:
+            slave-label: fuel-virtual
+            <<: *master
+        - baremetal:
+            slave-label: fuel-baremetal
+            <<: *danube
+        - virtual:
+            slave-label: fuel-virtual
+            <<: *danube
+#--------------------------------
+#       scenarios
+#--------------------------------
+    scenario:
+        # HA scenarios
+        - 'os-nosdn-nofeature-ha':
+            auto-trigger-name: 'weekly-trigger-disabled'
+
+    jobs:
+        - 'fuel-{scenario}-{pod}-weekly-{stream}'
+        - 'fuel-deploy-{pod}-weekly-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: 'fuel-{scenario}-{pod}-weekly-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'fuel-os-.*?-{pod}-daily-.*'
+                - 'fuel-os-.*?-{pod}-weekly-.*'
+            block-level: 'NODE'
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+
+    triggers:
+        - '{auto-trigger-name}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{installer}-defaults'
+        - '{slave-label}-defaults':
+            installer: '{installer}'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: '{scenario}'
+        - fuel-weekly-parameter:
+            gs-pathname: '{gs-pathname}'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - trigger-builds:
+            - project: 'fuel-deploy-{pod}-weekly-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              same-node: true
+              block: true
+        - trigger-builds:
+            - project: 'functest-fuel-{pod}-weekly-{stream}'
+              current-parameters: false
+              predefined-parameters:
+                DEPLOY_SCENARIO={scenario}
+              same-node: true
+              block: true
+              block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+
+    publishers:
+        - email:
+            recipients: peter.barabas@ericsson.com fzhadaev@mirantis.com
+
+- job-template:
+    name: 'fuel-deploy-{pod}-weekly-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 4
+            max-per-node: 1
+            option: 'project'
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'fuel-deploy-{pod}-daily-.*'
+                - 'fuel-deploy-generic-daily-.*'
+                - 'fuel-deploy-{pod}-weekly-.*'
+                - 'fuel-deploy-generic-weekly-.*'
+            block-level: 'NODE'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{installer}-defaults'
+        - '{slave-label}-defaults':
+            installer: '{installer}'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-odl_l2-nofeature-ha'
+        - fuel-weekly-parameter:
+            gs-pathname: '{gs-pathname}'
+        - string:
+            name: DEPLOY_TIMEOUT
+            default: '150'
+            description: 'Deployment timeout in minutes'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER - Scenario: $DEPLOY_SCENARIO'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - shell:
+            !include-raw-escape: ./fuel-download-artifact.sh
+        - shell:
+            !include-raw-escape: ./fuel-deploy.sh
+
+    publishers:
+        - email:
+            recipients: peter.barabas@ericsson.com fzhadaev@mirantis.com
+
+########################
+# parameter macros
+########################
+- parameter:
+    name: fuel-weekly-parameter
+    parameters:
+        - string:
+            name: BUILD_DIRECTORY
+            default: $WORKSPACE/build_output
+            description: "Directory where the build artifact will be located upon the completion of the build."
+        - string:
+            name: CACHE_DIRECTORY
+            default: $HOME/opnfv/cache/$INSTALLER_TYPE
+            description: "Directory where the cache to be used during the build is located."
+        - string:
+            name: GS_URL
+            default: artifacts.opnfv.org/$PROJECT{gs-pathname}
+            description: "URL to Google Storage."
+########################
+# trigger macros
+########################
+#-----------------------------------------------
+# Triggers for job running on fuel-baremetal against master branch
+#-----------------------------------------------
+# HA Scenarios
+- trigger:
+    name: 'fuel-os-nosdn-nofeature-ha-baremetal-weekly-master-trigger'
+    triggers:
+        - timed: ''
index 4bedfe7..fc277b9 100755 (executable)
@@ -3,19 +3,42 @@
 [[ $CI_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
 
 echo "Cleaning up docker containers/images..."
+HOST_ARCH=$(uname -m)
+FUNCTEST_IMAGE=opnfv/functest
+if [ "$HOST_ARCH" = "aarch64" ]; then
+    FUNCTEST_IMAGE="${FUNCTEST_IMAGE}_${HOST_ARCH}"
+fi
+
+# Remove containers along with image opnfv/functest*:<none>
+dangling_images=($(docker images -f "dangling=true" | grep $FUNCTEST_IMAGE | awk '{print $3}'))
+if [[ -n ${dangling_images} ]]; then
+    echo "  Removing $FUNCTEST_IMAGE:<none> images and their containers..."
+    for image_id in "${dangling_images[@]}"; do
+        echo "      Removing image_id: $image_id and its containers"
+        containers=$(docker ps -a | grep $image_id | awk '{print $1}')
+        if [[ -n "$containers" ]];then
+            docker rm -f $containers >${redirect}
+        fi
+        docker rmi $image_id >${redirect}
+    done
+fi
+
 # Remove previous running containers if exist
-if [[ ! -z $(docker ps -a | grep opnfv/functest) ]]; then
-    echo "Removing existing opnfv/functest containers..."
-    docker ps -a | grep opnfv/functest | awk '{print $1}' | xargs docker rm -f >${redirect}
+functest_containers=$(docker ps -a | grep $FUNCTEST_IMAGE | awk '{print $1}')
+if [[ -n ${functest_containers} ]]; then
+    echo "  Removing existing $FUNCTEST_IMAGE containers..."
+    docker rm -f $functest_containers >${redirect}
 fi
 
 # Remove existing images if exist
-if [[ ! -z $(docker images | grep opnfv/functest) ]]; then
-    echo "Docker images to remove:"
-    docker images | head -1 && docker images | grep opnfv/functest >${redirect}
-    image_tags=($(docker images | grep opnfv/functest | awk '{print $2}'))
-    for tag in "${image_tags[@]}"; do
-        echo "Removing docker image opnfv/functest:$tag..."
-        docker rmi opnfv/functest:$tag >/dev/null
-    done
+if [[ $CLEAN_DOCKER_IMAGES == true ]]; then
+    functest_image_tags=($(docker images | grep $FUNCTEST_IMAGE | awk '{print $2}'))
+    if [[ -n ${functest_image_tags} ]]; then
+        echo "  Docker images to be removed:" >${redirect}
+        (docker images | head -1 && docker images | grep $FUNCTEST_IMAGE) >${redirect}
+        for tag in "${functest_image_tags[@]}"; do
+            echo "      Removing docker image $FUNCTEST_IMAGE:$tag..."
+            docker rmi $FUNCTEST_IMAGE:$tag >${redirect}
+        done
+    fi
 fi
similarity index 84%
rename from jjb/functest/functest-ci-jobs.yml
rename to jjb/functest/functest-daily-jobs.yml
index afeb1f9..e8d1432 100644 (file)
@@ -2,9 +2,9 @@
 # job configuration for functest
 ###################################
 - project:
-    name: functest
+    name: functest-daily
 
-    project: '{name}'
+    project: functest
 
 #--------------------------------
 # BRANCH ANCHORS
@@ -14,8 +14,8 @@
         branch: '{stream}'
         gs-pathname: ''
         docker-tag: 'latest'
-    colorado: &colorado
-        stream: colorado
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         gs-pathname: '/{stream}'
         docker-tag: 'stable'
         - baremetal:
             slave-label: fuel-baremetal
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: fuel-virtual
             installer: fuel
-            <<: *colorado
+            <<: *danube
 # joid CI PODs
         - baremetal:
             slave-label: joid-baremetal
         - baremetal:
             slave-label: joid-baremetal
             installer: joid
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: joid-virtual
             installer: joid
-            <<: *colorado
+            <<: *danube
 # compass CI PODs
         - baremetal:
             slave-label: compass-baremetal
         - baremetal:
             slave-label: compass-baremetal
             installer: compass
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: compass-virtual
             installer: compass
-            <<: *colorado
+            <<: *danube
 # apex CI PODs
         - apex-verify-master:
             slave-label: '{pod}'
             slave-label: '{pod}'
             installer: apex
             <<: *master
-        - apex-verify-colorado:
+        - apex-verify-danube:
             slave-label: '{pod}'
             installer: apex
-            <<: *colorado
-        - apex-daily-colorado:
+            <<: *danube
+        - apex-daily-danube:
             slave-label: '{pod}'
             installer: apex
-            <<: *colorado
+            <<: *danube
 # armband CI PODs
         - armband-baremetal:
             slave-label: armband-baremetal
         - armband-baremetal:
             slave-label: armband-baremetal
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - armband-virtual:
             slave-label: armband-virtual
             installer: fuel
-            <<: *colorado
+            <<: *danube
+# daisy CI PODs
+        - baremetal:
+            slave-label: daisy-baremetal
+            installer: daisy
+            <<: *master
+        - virtual:
+            slave-label: daisy-virtual
+            installer: daisy
+            <<: *master
+# netvirt 3rd party ci
+        - virtual:
+            slave-label: odl-netvirt-virtual
+            installer: netvirt
+            <<: *master
 #--------------------------------
 #        None-CI PODs
 #--------------------------------
             slave-label: '{pod}'
             installer: joid
             <<: *master
-        - huawei-pod5:
-            slave-label: '{pod}'
+        - baremetal-centos:
+            slave-label: 'intel-pod8'
             installer: compass
             <<: *master
         - nokia-pod1:
             slave-label: '{pod}'
             installer: fuel
             <<: *master
+        - arm-pod3-2:
+            slave-label: '{pod}'
+            installer: fuel
+            <<: *master
         - zte-pod1:
             slave-label: '{pod}'
             installer: fuel
         - zte-pod1:
             slave-label: '{pod}'
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - zte-pod2:
             slave-label: '{pod}'
             installer: fuel
         - zte-pod3:
             slave-label: '{pod}'
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - arm-pod2:
             slave-label: '{pod}'
             installer: fuel
-            <<: *colorado
+            <<: *danube
         - arm-pod3:
             slave-label: '{pod}'
             installer: fuel
-            <<: *colorado
+            <<: *danube
+        - arm-pod3-2:
+            slave-label: '{pod}'
+            installer: fuel
+            <<: *danube
 # PODs for verify jobs triggered by each patch upload
         - ool-virtual1:
             slave-label: '{pod}'
         - 'suite':
             job-timeout: 60
         - 'daily':
-            job-timeout: 180
-        - 'weekly':
-            job-timeout: 400
+            job-timeout: 240
 
     jobs:
         - 'functest-{installer}-{pod}-{testsuite}-{stream}'
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults'
         - 'functest-{testsuite}-parameter'
             name: DOCKER_TAG
             default: '{docker-tag}'
             description: 'Tag to pull docker image'
+        - string:
+            name: CLEAN_DOCKER_IMAGES
+            default: 'false'
+            description: 'Remove downloaded docker images (opnfv/functest*:*)'
         - functest-parameter:
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - description-setter:
-            description: "POD: $NODE_NAME"
+            description: "Built on $NODE_NAME"
         - 'functest-{testsuite}-builder'
 
 ########################
             name: FUNCTEST_SUITE_NAME
             default: 'daily'
             description: "Daily suite name to run"
-- parameter:
-    name: functest-weekly-parameter
-    parameters:
-        - string:
-            name: FUNCTEST_SUITE_NAME
-            default: 'weekly'
-            description: "Weekly suite name to run"
 - parameter:
     name: functest-suite-parameter
     parameters:
                 - 'tempest_smoke_serial'
                 - 'rally_sanity'
                 - 'odl'
+                - 'odl_netvirt'
                 - 'onos'
                 - 'promise'
                 - 'doctor'
         - string:
             name: TESTCASE_OPTIONS
             default: ''
-            description: 'Addtional parameters specific to test case(s)'
+            description: 'Additional parameters specific to test case(s)'
 - parameter:
     name: functest-parameter
     parameters:
             name: CI_DEBUG
             default: 'false'
             description: "Show debug output information"
+        - string:
+            name: RC_FILE_PATH
+            default: ''
+            description: "Path to the OS credentials file if given"
 ########################
 # trigger macros
 ########################
         - 'functest-store-results'
         - 'functest-exit'
 
-- builder:
-    name: functest-weekly-builder
-    builders:
-        - 'functest-cleanup'
-        - 'set-functest-env'
-        - 'functest-weekly'
-        - 'functest-store-results'
-        - 'functest-exit'
-
 - builder:
     name: functest-suite-builder
     builders:
         - shell:
             !include-raw: ./functest-loop.sh
 
-- builder:
-    name: functest-weekly
-    builders:
-        - shell:
-            !include-raw: ./functest-loop.sh
 
 - builder:
     name: functest-suite
index 10edab0..925a3cf 100644 (file)
@@ -1,7 +1,6 @@
 #!/bin/bash
 
-branch=${GIT_BRANCH##*/}
-ret_val_file="${HOME}/opnfv/functest/results/${branch}/return_value"
+ret_val_file="${HOME}/opnfv/functest/results/${BRANCH##*/}/return_value"
 if [ ! -f ${ret_val_file} ]; then
     echo "Return value not found!"
     exit -1
@@ -9,4 +8,4 @@ fi
 
 ret_val=`cat ${ret_val_file}`
 
-exit ${ret_val}
\ No newline at end of file
+exit ${ret_val}
index 7385623..893c428 100755 (executable)
@@ -3,16 +3,18 @@ set +e
 
 branch=${GIT_BRANCH##*/}
 [[ "$PUSH_RESULTS_TO_DB" == "true" ]] && flags+="-r"
-if [[ ${branch} == *"brahmaputra"* ]]; then
+if [[ "$BRANCH" =~ 'brahmaputra' ]]; then
     cmd="${FUNCTEST_REPO_DIR}/docker/run_tests.sh -s ${flags}"
-else
+elif [[ "$BRANCH" =~ 'colorado' ]]; then
     cmd="python ${FUNCTEST_REPO_DIR}/ci/run_tests.py -t all ${flags}"
+else
+    cmd="python ${FUNCTEST_REPO_DIR}/functest/ci/run_tests.py -t all ${flags}"
 fi
 container_id=$(docker ps -a | grep opnfv/functest | awk '{print $1}' | head -1)
 docker exec $container_id $cmd
 
 ret_value=$?
-ret_val_file="${HOME}/opnfv/functest/results/${branch}/return_value"
+ret_val_file="${HOME}/opnfv/functest/results/${BRANCH##*/}/return_value"
 echo ${ret_value}>${ret_val_file}
 
-exit 0
\ No newline at end of file
+exit 0
index 236b95d..14ad73a 100644 (file)
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
-            disabled: true
+            disabled: false
 
 - job-template:
     name: 'functest-verify-{stream}'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index 7e9fa09..228cc3d 100755 (executable)
@@ -1,12 +1,18 @@
 #!/bin/bash
-set -e
-
-branch=${GIT_BRANCH##*/}
-echo "Functest: run $FUNCTEST_SUITE_NAME on branch ${branch}"
-if [[ ${branch} == *"brahmaputra"* ]]; then
-    cmd="${FUNCTEST_REPO_DIR}/docker/run_tests.sh --test $FUNCTEST_SUITE_NAME"
-else
-    cmd="python ${FUNCTEST_REPO_DIR}/ci/run_tests.py -t $FUNCTEST_SUITE_NAME"
-fi
+
 container_id=$(docker ps -a | grep opnfv/functest | awk '{print $1}' | head -1)
-docker exec $container_id $cmd
+if [ -z $container_id ]; then
+    echo "Functest container not found"
+    exit 1
+fi
+
+global_ret_val=0
+
+tests=($(echo $FUNCTEST_SUITE_NAME | tr "," "\n"))
+for test in ${tests[@]}; do
+    cmd="python /home/opnfv/repos/functest/functest/ci/run_tests.py -t $test"
+    docker exec $container_id $cmd
+    let global_ret_val+=$?
+done
+
+exit $global_ret_val
diff --git a/jjb/functest/functest-weekly-jobs.yml b/jjb/functest/functest-weekly-jobs.yml
new file mode 100644 (file)
index 0000000..f44f7b8
--- /dev/null
@@ -0,0 +1,124 @@
+###################################
+# job configuration for functest
+###################################
+- project:
+    name: functest-weekly
+
+    project: functest
+
+#--------------------------------
+# BRANCH ANCHORS
+#--------------------------------
+    master: &master
+        stream: master
+        branch: '{stream}'
+        gs-pathname: ''
+        docker-tag: 'latest'
+        disabled: false
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        gs-pathname: '/{stream}'
+        docker-tag: 'stable'
+        disabled: true
+#--------------------------------
+# POD, INSTALLER, AND BRANCH MAPPING
+#--------------------------------
+#    Installers using labels
+#            CI PODs
+# This section should only contain the installers
+# that have been switched using labels for slaves
+#--------------------------------
+    pod:
+# fuel CI PODs
+        - baremetal:
+            slave-label: fuel-baremetal
+            installer: fuel
+            <<: *master
+        - virtual:
+            slave-label: fuel-virtual
+            installer: fuel
+            <<: *master
+        - baremetal:
+            slave-label: fuel-baremetal
+            installer: fuel
+            <<: *danube
+        - virtual:
+            slave-label: fuel-virtual
+            installer: fuel
+            <<: *danube
+#--------------------------------
+    jobs:
+        - 'functest-{installer}-{pod}-weekly-{stream}'
+
+################################
+# job template
+################################
+- job-template:
+    name: 'functest-{installer}-{pod}-weekly-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-per-node: 1
+            option: 'project'
+
+    wrappers:
+        - build-name:
+            name: '$BUILD_NUMBER Suite: $FUNCTEST_SUITE_NAME Scenario: $DEPLOY_SCENARIO'
+        - timeout:
+            timeout: '400'
+            abort: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{installer}-defaults'
+        - '{slave-label}-defaults'
+        - string:
+            name: FUNCTEST_SUITE_NAME
+            default: 'weekly'
+            description: "Weekly suite name to run"
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-odl_l2-nofeature-ha'
+        - string:
+            name: DOCKER_TAG
+            default: '{docker-tag}'
+            description: 'Tag to pull docker image'
+        - string:
+            name: CLEAN_DOCKER_IMAGES
+            default: 'false'
+            description: 'Remove downloaded docker images (opnfv/functest*:*)'
+        - functest-parameter:
+            gs-pathname: '{gs-pathname}'
+
+    scm:
+        - git-scm
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - 'functest-weekly-builder'
+########################
+# builder macros
+########################
+- builder:
+    name: functest-weekly-builder
+    builders:
+        - shell:
+            !include-raw: ./functest-cleanup.sh
+        - shell:
+            !include-raw: ./set-functest-env.sh
+        - shell:
+            !include-raw: ./functest-loop.sh
+        - shell:
+            !include-raw: ../../utils/push-test-logs.sh
+        - shell:
+            !include-raw: ./functest-exit.sh
index 1c77702..05e3d57 100755 (executable)
@@ -2,40 +2,53 @@
 
 set -e
 [[ $CI_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
-# labconfig is used only for joid
-labconfig=""
+# LAB_CONFIG is used only for joid
+
+
+if [[ ${INSTALLER_TYPE} == 'joid' ]]; then
+    # If production lab then creds may be retrieved dynamically
+    # creds are on the jumphost, always in the same folder
+    rc_file_vol="-v $LAB_CONFIG/admin-openrc:/home/opnfv/functest/conf/openstack.creds"
+    # If dev lab, credentials may not be the default ones, just provide a path to put them into docker
+    # replace the default one by the customized one provided by jenkins config
+fi
+
+if [[ ${RC_FILE_PATH} != '' ]] && [[ -f ${RC_FILE_PATH} ]] ; then
+    echo "Credentials file detected: ${RC_FILE_PATH}"
+    # volume if credentials file path is given to Functest
+    rc_file_vol="-v ${RC_FILE_PATH}:/home/opnfv/functest/conf/openstack.creds"
+    RC_FLAG=1
+fi
+
+
 if [[ ${INSTALLER_TYPE} == 'apex' ]]; then
     ssh_options="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
-    if sudo virsh list | grep instack; then
-        instack_mac=$(sudo virsh domiflist instack | grep default | \
-                      grep -Eo "[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+")
-    elif sudo virsh list | grep undercloud; then
-        instack_mac=$(sudo virsh domiflist undercloud | grep default | \
+    if sudo virsh list | grep undercloud; then
+        echo "Installer VM detected"
+        undercloud_mac=$(sudo virsh domiflist undercloud | grep default | \
                       grep -Eo "[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+")
+        INSTALLER_IP=$(/usr/sbin/arp -e | grep ${undercloud_mac} | awk {'print $1'})
+        sshkey_vol="-v /root/.ssh/id_rsa:/root/.ssh/id_rsa"
+        sudo scp $ssh_options root@${INSTALLER_IP}:/home/stack/stackrc ${HOME}/stackrc
+        stackrc_vol="-v ${HOME}/stackrc:/home/opnfv/functest/conf/stackrc"
+
+        if sudo iptables -C FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable 2> ${redirect}; then
+            sudo iptables -D FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable
+        fi
+        if sudo iptables -C FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable 2> ${redirect}; then
+          sudo iptables -D FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable
+        fi
+    elif [[ "$RC_FLAG" == 1 ]]; then
+        echo "No available installer VM, but credentials provided...continuing"
     else
-        echo "No available installer VM exists...exiting"
+        echo "No available installer VM exists and no credentials provided...exiting"
         exit 1
     fi
-    INSTALLER_IP=$(/usr/sbin/arp -e | grep ${instack_mac} | awk {'print $1'})
-    sshkey="-v /root/.ssh/id_rsa:/root/.ssh/id_rsa"
-    sudo scp $ssh_options root@${INSTALLER_IP}:/home/stack/stackrc ${HOME}/stackrc
-    stackrc="-v ${HOME}/stackrc:/home/opnfv/functest/conf/stackrc"
 
-    if sudo iptables -C FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable 2> ${redirect}; then
-        sudo iptables -D FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable
-    fi
-    if sudo iptables -C FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable 2> ${redirect}; then
-        sudo iptables -D FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable
-    fi
-
-elif [[ ${INSTALLER_TYPE} == 'joid' ]]; then
-    # If production lab then creds may be retrieved dynamically
-    # creds are on the jumphost, always in the same folder
-    labconfig="-v $LAB_CONFIG/admin-openrc:/home/opnfv/functest/conf/openstack.creds"
-    # If dev lab, credentials may not be the default ones, just provide a path to put them into docker
-    # replace the default one by the customized one provided by jenkins config
 fi
 
+
+
 # Set iptables rule to allow forwarding return traffic for container
 if ! sudo iptables -C FORWARD -j RETURN 2> ${redirect} || ! sudo iptables -L FORWARD | awk 'NR==3' | grep RETURN 2> ${redirect}; then
     sudo iptables -I FORWARD -j RETURN
@@ -45,27 +58,37 @@ DEPLOY_TYPE=baremetal
 [[ $BUILD_TAG =~ "virtual" ]] && DEPLOY_TYPE=virt
 
 echo "Functest: Start Docker and prepare environment"
-envs="-e INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP} \
-    -e NODE_NAME=${NODE_NAME} -e DEPLOY_SCENARIO=${DEPLOY_SCENARIO} \
-    -e BUILD_TAG=${BUILD_TAG} -e CI_DEBUG=${CI_DEBUG} -e DEPLOY_TYPE=${DEPLOY_TYPE}"
-branch=${GIT_BRANCH##*/}
-dir_result="${HOME}/opnfv/functest/results/${branch}"
+
+dir_result="${HOME}/opnfv/functest/results/${BRANCH##*/}"
 mkdir -p ${dir_result}
 sudo rm -rf ${dir_result}/*
-res_volume="-v ${dir_result}:/home/opnfv/functest/results"
+results_vol="-v ${dir_result}:/home/opnfv/functest/results"
 custom_params=
 test -f ${HOME}/opnfv/functest/custom/params_${DOCKER_TAG} && custom_params=$(cat ${HOME}/opnfv/functest/custom/params_${DOCKER_TAG})
 
-echo "Functest: Pulling image opnfv/functest:${DOCKER_TAG}"
-docker pull opnfv/functest:$DOCKER_TAG >/dev/null
+envs="-e INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP} \
+    -e NODE_NAME=${NODE_NAME} -e DEPLOY_SCENARIO=${DEPLOY_SCENARIO} \
+    -e BUILD_TAG=${BUILD_TAG} -e CI_DEBUG=${CI_DEBUG} -e DEPLOY_TYPE=${DEPLOY_TYPE}"
+
 
-cmd="sudo docker run --privileged=true -id ${envs} ${labconfig} ${sshkey} \
-     ${res_volume} ${custom_params} ${stackrc} ${TESTCASE_OPTIONS} \
-     opnfv/functest:${DOCKER_TAG} /bin/bash"
+volumes="${results_vol} ${sshkey_vol} ${stackrc_vol} ${rc_file_vol}"
+
+HOST_ARCH=$(uname -m)
+FUNCTEST_IMAGE="opnfv/functest"
+if [ "$HOST_ARCH" = "aarch64" ]; then
+    FUNCTEST_IMAGE="${FUNCTEST_IMAGE}_${HOST_ARCH}"
+fi
+
+echo "Functest: Pulling image ${FUNCTEST_IMAGE}:${DOCKER_TAG}"
+docker pull ${FUNCTEST_IMAGE}:$DOCKER_TAG >/dev/null
+
+cmd="sudo docker run --privileged=true -id ${envs} ${volumes} \
+     ${custom_params} ${TESTCASE_OPTIONS} \
+     ${FUNCTEST_IMAGE}:${DOCKER_TAG} /bin/bash"
 echo "Functest: Running docker run command: ${cmd}"
 ${cmd} >${redirect}
 sleep 5
-container_id=$(docker ps | grep "opnfv/functest:${DOCKER_TAG}" | awk '{print $1}' | head -1)
+container_id=$(docker ps | grep "${FUNCTEST_IMAGE}:${DOCKER_TAG}" | awk '{print $1}' | head -1)
 echo "Container ID=${container_id}"
 if [ -z ${container_id} ]; then
     echo "Cannot find opnfv/functest container ID ${container_id}. Please check if it is existing."
@@ -76,14 +99,16 @@ echo "Starting the container: docker start ${container_id}"
 docker start ${container_id}
 sleep 5
 docker ps >${redirect}
-if [ $(docker ps | grep "opnfv/functest:${DOCKER_TAG}" | wc -l) == 0 ]; then
-    echo "The container opnfv/functest with ID=${container_id} has not been properly started. Exiting..."
+if [ $(docker ps | grep "${FUNCTEST_IMAGE}:${DOCKER_TAG}" | wc -l) == 0 ]; then
+    echo "The container ${FUNCTEST_IMAGE} with ID=${container_id} has not been properly started. Exiting..."
     exit 1
 fi
-if [[ ${branch} == *"brahmaputra"* ]]; then
+if [[ "$BRANCH" =~ 'brahmaputra' ]]; then
     cmd="${FUNCTEST_REPO_DIR}/docker/prepare_env.sh"
-else
+elif [[ "$BRANCH" =~ 'colorado' ]]; then
     cmd="python ${FUNCTEST_REPO_DIR}/ci/prepare_env.py start"
+else
+    cmd="python ${FUNCTEST_REPO_DIR}/functest/ci/prepare_env.py start"
 fi
 echo "Executing command inside the docker: ${cmd}"
 docker exec ${container_id} ${cmd}
similarity index 73%
rename from jjb/opnfv/installer-params.yml
rename to jjb/global/installer-params.yml
index ec0b861..fc9f34a 100644 (file)
@@ -9,10 +9,6 @@
             name: INSTALLER_TYPE
             default: apex
             description: 'Installer used for deploying OPNFV on this POD'
-        - string:
-            name: DEPLOY_SCENARIO
-            default: 'none'
-            description: 'Scenario to deploy and test'
         - string:
             name: EXTERNAL_NETWORK
             default: 'external'
     parameters:
         - string:
             name: INSTALLER_IP
-            default: '192.168.Y.Y'
+            default: '192.168.122.5'
             description: 'IP of the installer'
         - string:
             name: INSTALLER_TYPE
             default: joid
             description: 'Installer used for deploying OPNFV on this POD'
+        - string:
+            name: MODEL
+            default: 'os'
+            description: 'Model to deploy (os|k8)'
         - string:
             name: OS_RELEASE
-            default: 'mitaka'
-            description: 'OpenStack release (liberty|mitaka)'
+            default: 'newton'
+            description: 'OpenStack release (mitaka|newton)'
         - string:
             name: EXTERNAL_NETWORK
-            default: ext-net4
+            default: ext-net
             description: "External network used for Floating ips."
         - string:
             name: LAB_CONFIG
         - string:
             name: UBUNTU_DISTRO
             default: 'xenial'
-            description: "Ubuntu distribution to use for Openstack (trusty|xenial)"
+            description: "Ubuntu distribution to use for Openstack (xenial)"
         - string:
             name: CPU_ARCHITECTURE
             default: 'amd64'
             description: "CPU Architecture to use for Ubuntu distro "
+
+- parameter:
+    name: 'daisy-defaults'
+    parameters:
+        - string:
+            name: INSTALLER_IP
+            default: '10.20.0.2'
+            description: 'IP of the installer'
+        - string:
+            name: INSTALLER_TYPE
+            default: daisy
+            description: 'Installer used for deploying OPNFV on this POD'
+
 - parameter:
     name: 'infra-defaults'
     parameters:
             name: INSTALLER_TYPE
             default: infra
             description: 'Installer used for deploying OPNFV on this POD'
+- parameter:
+    name: 'netvirt-defaults'
+    parameters:
+        - string:
+            name: INSTALLER_IP
+            default: '192.168.X.X'
+            description: 'IP of the installer'
+        - string:
+            name: INSTALLER_TYPE
+            default: apex
+            description: 'Installer used for deploying OPNFV on this POD'
+        - string:
+            name: EXTERNAL_NETWORK
+            default: 'external'
+            description: 'external network for test'
diff --git a/jjb/global/releng-defaults.yml b/jjb/global/releng-defaults.yml
new file mode 100644 (file)
index 0000000..2838886
--- /dev/null
@@ -0,0 +1,14 @@
+# jjb defaults
+
+- defaults:
+    name: global
+
+    wrappers:
+        - ssh-agent-wrapper
+
+    project-type: freestyle
+
+    node: master
+
+    properties:
+        - logrotate-default
similarity index 69%
rename from jjb/releng-macros.yaml
rename to jjb/global/releng-macros.yml
index 3afd355..ced335c 100644 (file)
@@ -1,4 +1,15 @@
-# OLD Releng macros
+# Releng macros
+#
+# NOTE: make sure macros are listed in execution ordered.
+#
+# 1. parameters/properties
+# 2. scm
+# 3. triggers
+# 4. wrappers
+# 5. prebuilders (maven only, configured like Builders)
+# 6. builders (maven, freestyle, matrix, etc..)
+# 7. postbuilders (maven only, configured like Builders)
+# 8. publishers/reporters/notifications
 
 - parameter:
     name: project-parameter
             name: GS_BASE_PROXY
             default: build.opnfv.org/artifacts.opnfv.org/$PROJECT
             description: "URL to Google Storage proxy"
-
-- parameter:
-    name: gerrit-parameter
-    parameters:
+        - string:
+            name: BRANCH
+            default: '{branch}'
+            description: "JJB configured BRANCH parameter (e.g. master, stable/danube)"
         - string:
             name: GERRIT_BRANCH
             default: '{branch}'
-            description: "JJB configured GERRIT_BRANCH parameter"
+            description: "JJB configured GERRIT_BRANCH parameter (deprecated)"
+
+- property:
+    name: logrotate-default
+    properties:
+        - build-discarder:
+            days-to-keep: 60
+            num-to-keep: 200
+            artifact-days-to-keep: 60
+            artifact-num-to-keep: 200
 
 - scm:
     name: git-scm
     scm:
-        - git:
-            credentials-id: '{credentials-id}'
+        - git: &git-scm-defaults
+            credentials-id: '$SSH_CREDENTIAL_ID'
             url: '$GIT_BASE'
-            refspec: ''
             branches:
-                - 'origin/{branch}'
-            skip-tag: true
-            wipe-workspace: true
+                - 'origin/$BRANCH'
+            timeout: 15
 
 - scm:
-    name: gerrit-trigger-scm
+    name: git-scm-gerrit
+    scm:
+        - git:
+            choosing-strategy: 'gerrit'
+            refspec: '$GERRIT_REFSPEC'
+            <<: *git-scm-defaults
+- scm:
+    name: git-scm-with-submodules
     scm:
         - git:
-            credentials-id: '{credentials-id}'
+            credentials-id: '$SSH_CREDENTIAL_ID'
             url: '$GIT_BASE'
-            refspec: '{refspec}'
+            refspec: ''
             branches:
-                - 'origin/$GERRIT_BRANCH'
+                - 'refs/heads/{branch}'
             skip-tag: true
-            choosing-strategy: '{choosing-strategy}'
-            timeout: 15
-
+            wipe-workspace: true
+            submodule:
+                recursive: true
+                timeout: 20
 - trigger:
     name: 'daily-trigger-disabled'
     triggers:
@@ -60,7 +86,7 @@
         - timed: ''
 
 - trigger:
-    name: gerrit-trigger-patch-submitted
+    name: gerrit-trigger-patchset-created
     triggers:
         - gerrit:
             server-name: 'gerrit.opnfv.org'
                 - draft-published-event
                 - comment-added-contains-event:
                     comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
             projects:
               - project-compare-type: 'ANT'
-                project-pattern: '{name}'
+                project-pattern: '{project}'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: 'ANT'
+                    pattern: '{files}'
+            skip-vote:
+                successful: false
+                failed: false
+                unstable: false
+                notbuilt: false
 
 - trigger:
-    name: gerrit-trigger-patch-merged
+    name: gerrit-trigger-change-merged
     triggers:
         - gerrit:
             server-name: 'gerrit.opnfv.org'
                     comment-contains-value: 'remerge'
             projects:
               - project-compare-type: 'ANT'
-                project-pattern: '{name}'
+                project-pattern: '{project}'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: 'ANT'
+                    pattern: '{files}'
 
-- publisher:
-    name: archive-artifacts
-    publishers:
-        - archive:
-            artifacts: '{artifacts}'
-            allow-empty: true
-            fingerprint: true
-            latest-only: true
-
-# New Releng macros
+- trigger:
+    name: 'experimental'
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - comment-added-contains-event:
+                    comment-contains-value: 'check-experimental'
+            projects:
+                - project-compare-type: 'ANT'
+                  project-pattern: '{project}'
+                  branches:
+                      - branch-compare-type: 'ANT'
+                        branch-pattern: '**/{branch}'
+                  file-paths:
+                      - compare-type: 'ANT'
+                        pattern: 'tests/**'
+            skip-vote:
+                successful: true
+                failed: true
+                unstable: true
+                notbuilt: true
+
+- wrapper:
+    name: ssh-agent-wrapper
+    wrappers:
+        - ssh-agent-credentials:
+            users:
+                - 'd42411ac011ad6f3dd2e1fa34eaa5d87f910eb2e'
+
+- wrapper:
+    name: fix-workspace-permissions
+    wrappers:
+        - pre-scm-buildstep:
+          - shell: |
+                #!/bin/bash
+                sudo chown -R $USER $WORKSPACE || exit 1
 
 - builder:
     name: build-html-and-pdf-docs-output
     name: check-bash-syntax
     builders:
         - shell: "find . -name '*.sh' | xargs bash -n"
+
+- builder:
+    name: lint-yaml-code
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o pipefail
+            set -o xtrace
+            export PATH=$PATH:/usr/local/bin/
+
+            # install python packages
+            pip install "yamllint==1.6.0"
+
+            # generate and upload lint log
+            echo "Running yaml code on $PROJECT ..."
+
+            # Ensure we start with a clean environment
+            rm -f yaml-violation.log lint.log
+
+            # Get number of yaml violations. If none, this will be an
+            # empty string: ""
+            find . \
+                -type f -name "*.yml" -print \
+                -o -name "*.yaml" -print | \
+                xargs yamllint > yaml-violation.log || true
+
+            if [ -s "yaml-violation.log" ]; then
+              SHOWN=$(cat yaml-violation.log| grep -v "^$" |wc -l)
+              echo -e "First $SHOWN shown\n---" > lint.log
+              cat yaml-violation.log >> lint.log
+              sed -r -i '4,$s/^/ /g' lint.log
+            fi
+
+- builder:
+    name: clean-workspace-log
+    builders:
+        - shell: |
+            find $WORKSPACE -type f -name '*.log' | xargs rm -f
+
+- publisher:
+    name: archive-artifacts
+    publishers:
+        - archive:
+            artifacts: '{artifacts}'
+            allow-empty: true
+            fingerprint: true
+            latest-only: true
+
+- publisher:
+    name: publish-coverage
+    publishers:
+      - cobertura:
+          report-file: "coverage.xml"
+          only-stable: "true"
+          health-auto-update: "false"
+          stability-auto-update: "false"
+          zoom-coverage-chart: "true"
+          targets:
+            - files:
+                healthy: 10
+                unhealthy: 20
+                failing: 30
+            - method:
+                healthy: 50
+                unhealthy: 40
+                failing: 30
+
similarity index 76%
rename from jjb/opnfv/slave-params.yml
rename to jjb/global/slave-params.yml
index b46960f..1905a09 100644 (file)
             default-slaves:
                 - lf-pod1
 - parameter:
-    name: 'apex-daily-colorado-defaults'
+    name: 'apex-daily-danube-defaults'
     parameters:
         - label:
             name: SLAVE_LABEL
-            default: 'apex-daily-colorado'
+            default: 'apex-daily-danube'
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
                 - intel-virtual4
                 - intel-virtual5
 - parameter:
-    name: 'apex-verify-colorado-defaults'
+    name: 'apex-verify-danube-defaults'
     parameters:
         - label:
             name: SLAVE_LABEL
-            default: 'apex-verify-colorado'
+            default: 'apex-verify-danube'
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             name: EXTERNAL_NETWORK
             default: ext-net
             description: "External network floating ips"
+- parameter:
+    name: 'daisy-baremetal-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - zte-pod2
+            default-slaves:
+                - zte-pod2
+        - label:
+            name: SLAVE_LABEL
+            default: 'daisy-baremetal'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
 #####################################################
 # Parameters for CI virtual PODs
 #####################################################
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'daisy-virtual-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - zte-virtual1
+                - zte-virtual2
+            default-slaves:
+                - zte-virtual1
+        - label:
+            name: SLAVE_LABEL
+            default: 'daisy-virtual'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
 #####################################################
 # Parameters for build slaves
 #####################################################
 - parameter:
-    name: 'opnfv-build-arm-defaults'
+    name: 'opnfv-build-enea-defaults'
     parameters:
         - label:
             name: SLAVE_LABEL
-            default: 'opnfv-build-arm'
+            default: 'opnfv-build-enea'
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'opnfv-build-ubuntu-arm-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'opnfv-build-ubuntu-arm'
+            description: 'Slave label on Jenkins'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+        - string:
+            name: BUILD_DIRECTORY
+            default: $WORKSPACE/build_output
+            description: "Directory where the build artifact will be located upon the completion of the build."
 #####################################################
 # Parameters for none-CI PODs
 #####################################################
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'cengn-pod1-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - cengn-pod1
+            default-slaves:
+                - cengn-pod1
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
 - parameter:
     name: 'intel-pod1-defaults'
     parameters:
             default: /root/.ssh/id_rsa
             description: 'SSH key to use for Apex'
 - parameter:
-    name: 'intel-pod3-defaults'
+    name: 'intel-pod9-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - intel-pod9
+            default-slaves:
+                - intel-pod9
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'intel-pod10-defaults'
     parameters:
         - node:
             name: SLAVE_NAME
             description: 'Slave name on Jenkins'
             allowed-slaves:
-                - intel-pod3
+                - intel-pod10
             default-slaves:
-                - intel-pod3
+                - intel-pod10
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'intel-pod12-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - intel-pod12
+            default-slaves:
+                - intel-pod12
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: 'Git URL to use on this Jenkins Slave'
 - parameter:
-    name: 'huawei-pod5-defaults'
+    name: 'intel-pod8-defaults'
     parameters:
         - node:
             name: SLAVE_NAME
             description: 'Slave name on Jenkins'
             allowed-slaves:
-                - huawei-pod5
+                - intel-pod8
             default-slaves:
-                - huawei-pod5
+                - intel-pod8
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+- parameter:
+    name: 'huawei-virtual7-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - huawei-virtual7
+            default-slaves:
+                - huawei-virtual7
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+- parameter:
+    name: 'huawei-pod7-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - huawei-pod7
+            default-slaves:
+                - huawei-pod7
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             name: LAB_CONFIG_URL
             default: ssh://jenkins-enea@gerrit.opnfv.org:29418/securedlab
             description: 'Base URI to the configuration directory'
+- parameter:
+    name: 'arm-pod3-2-defaults'
+    parameters:
+        - node:
+            name: SLAVE_NAME
+            description: 'Slave name on Jenkins'
+            allowed-slaves:
+                - arm-pod3-2
+            default-slaves:
+                - arm-pod3-2
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+        - string:
+            name: LAB_CONFIG_URL
+            default: ssh://jenkins-enea@gerrit.opnfv.org:29418/securedlab
+            description: 'Base URI to the configuration directory'
 - parameter:
     name: 'intel-virtual6-defaults'
     parameters:
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: 'Git URL to use on this Jenkins Slave'
 - parameter:
-    name: 'ool-virtual1-defaults'
+    name: 'ool-defaults'
     parameters:
         - node:
             name: SLAVE_NAME
             description: 'Slave name on Jenkins'
             allowed-slaves:
                 - ool-virtual1
+                - ool-virtual2
+                - ool-virtual3
             default-slaves:
-                - ool-virtual1
+                - '{default-slave}'
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             name: SSH_KEY
             default: /root/.ssh/id_rsa
             description: 'SSH key to be used'
+- parameter:
+    name: 'ool-virtual1-defaults'
+    parameters:
+        - 'ool-defaults':
+            default-slave: 'ool-virtual1'
+- parameter:
+    name: 'ool-virtual2-defaults'
+    parameters:
+        - 'ool-defaults':
+            default-slave: 'ool-virtual2'
+- parameter:
+    name: 'ool-virtual3-defaults'
+    parameters:
+        - 'ool-defaults':
+            default-slave: 'ool-virtual3'
+- parameter:
+    name: 'multisite-virtual-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'multisite-virtual'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'ericsson-virtual5-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'ericsson-virtual5'
+        - string:
+            name: GIT_BASE
+            default: https://git.opendaylight.org/gerrit/p/$PROJECT.git
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'ericsson-virtual12-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'ericsson-virtual12'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'ericsson-virtual13-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'ericsson-virtual13'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'odl-netvirt-virtual-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'odl-netvirt-virtual'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
+- parameter:
+    name: 'odl-netvirt-virtual-intel-defaults'
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'odl-netvirt-virtual-intel'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+            description: 'Git URL to use on this Jenkins Slave'
 #####################################################
 # These slaves are just dummy slaves for sandbox jobs
 #####################################################
diff --git a/jjb/infra/bifrost-verify.sh b/jjb/infra/bifrost-verify.sh
deleted file mode 100755 (executable)
index ded4ed4..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/bash
-# SPDX-license-identifier: Apache-2.0
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-set -o errexit
-set -o nounset
-set -o pipefail
-
-trap fix_ownership EXIT
-
-function fix_ownership() {
-    if [ -z "${JOB_URL+x}" ]; then
-        echo "Not running as part of Jenkins. Handle the logs manually."
-    else
-        sudo chown -R jenkins:jenkins $WORKSPACE
-        sudo chown -R jenkins:jenkins ${HOME}/.cache
-    fi
-}
-
-# check distro to see if we support it
-if [[ ! "$DISTRO" =~ (trusty|centos7|suse) ]]; then
-    echo "Distro $DISTRO is not supported!"
-    exit 1
-fi
-
-# remove previously cloned repos
-sudo /bin/rm -rf /opt/bifrost /opt/puppet-infracloud /opt/stack /opt/releng
-
-# Fix up permissions
-fix_ownership
-
-# clone all the repos first and checkout the patch afterwards
-sudo git clone https://git.openstack.org/openstack/bifrost /opt/bifrost
-sudo git clone https://git.openstack.org/openstack-infra/puppet-infracloud /opt/puppet-infracloud
-sudo git clone https://gerrit.opnfv.org/gerrit/releng /opt/releng
-
-# checkout the patch
-cd $CLONE_LOCATION
-sudo git fetch $PROJECT_REPO $GERRIT_REFSPEC && sudo git checkout FETCH_HEAD
-
-# combine opnfv and upstream scripts/playbooks
-sudo /bin/cp -rf /opt/releng/prototypes/bifrost/* /opt/bifrost/
-
-# place bridge creation file on the right path
-sudo mkdir -p /opt/puppet-infracloud/files/elements/infra-cloud-bridge/static/opt
-sudo cp /opt/puppet-infracloud/templates/bifrost/create_bridge.py.erb /opt/puppet-infracloud/files/elements/infra-cloud-bridge/static/opt/create_bridge.py
-
-# replace bridge name
-sudo sed -i s/"<%= @bridge_name -%>"/br_opnfv/g /opt/puppet-infracloud/files/elements/infra-cloud-bridge/static/opt/create_bridge.py
-
-# cleanup remnants of previous deployment
-cd /opt/bifrost
-sudo -E ./scripts/destroy-env.sh
-
-# provision 3 VMs; jumphost, controller, and compute
-cd /opt/bifrost
-sudo -E ./scripts/test-bifrost-deployment.sh
-
-# list the provisioned VMs
-cd /opt/bifrost
-source env-vars
-ironic node-list
-virsh list
index d9dafdf..b0db764 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index 6d03709..7dc7189 100644 (file)
@@ -17,8 +17,8 @@
         branch: '{stream}'
         disabled: false
         gs-pathname: ''
-    colorado: &colorado
-        stream: colorado
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         disabled: false
         gs-pathname: '/{stream}'
             <<: *master
         - baremetal:
             slave-label: joid-baremetal
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: joid-virtual
-            <<: *colorado
+            <<: *danube
 #--------------------------------
 #        None-CI PODs
 #--------------------------------
         - orange-pod1:
             slave-label: orange-pod1
             <<: *master
+        - cengn-pod1:
+            slave-label: cengn-pod1
+            <<: *master
 #--------------------------------
 # scenarios
 #--------------------------------
         - 'os-odl_l2-nofeature-ha':
             auto-trigger-name: 'joid-{scenario}-{pod}-{stream}-trigger'
         - 'os-onos-nofeature-ha':
-            auto-trigger-name: 'joid-{scenario}-{pod}-{stream}-trigger'
+            auto-trigger-name: 'daily-trigger-disabled'
         - 'os-odl_l2-nofeature-noha':
             auto-trigger-name: 'daily-trigger-disabled'
         - 'os-onos-nofeature-noha':
             auto-trigger-name: 'daily-trigger-disabled'
         - 'os-onos-sfc-ha':
-            auto-trigger-name: 'joid-{scenario}-{pod}-{stream}-trigger'
+            auto-trigger-name: 'daily-trigger-disabled'
         - 'os-ocl-nofeature-ha':
             auto-trigger-name: 'daily-trigger-disabled'
         - 'os-ocl-nofeature-noha':
             auto-trigger-name: 'daily-trigger-disabled'
+        - 'k8-nosdn-nofeature-noha':
+            auto-trigger-name: 'joid-{scenario}-{pod}-{stream}-trigger'
+        - 'k8-nosdn-lb-noha':
+            auto-trigger-name: 'joid-{scenario}-{pod}-{stream}-trigger'
 
     jobs:
         - 'joid-{scenario}-{pod}-daily-{stream}'
@@ -88,6 +95,7 @@
     concurrent: false
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults':
             installer: '{installer}'
                 build-step-failure-threshold: 'never'
                 failure-threshold: 'never'
                 unstable-threshold: 'FAILURE'
+        # 1.dovetail only master by now, not sync with A/B/C branches
+        # 2.here the stream means the SUT stream, dovetail stream is defined in its own job
+        # 3.only debug testsuite here(includes basic testcase,
+        #   i.e. one tempest smoke ipv6, two vping from functest)
+        # 4.not used for release criteria or compliance,
+        #   only to debug the dovetail tool bugs with joid
+        #- trigger-builds:
+        #    - project: 'dovetail-joid-{pod}-debug-{stream}'
+        #      current-parameters: false
+        #      predefined-parameters:
+        #        DEPLOY_SCENARIO={scenario}
+        #      block: true
+        #      same-node: true
+        #      block-thresholds:
+        #        build-step-failure-threshold: 'never'
+        #        failure-threshold: 'never'
+        #        unstable-threshold: 'FAILURE'
 
 - job-template:
     name: 'joid-deploy-{pod}-daily-{stream}'
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults':
             installer: '{installer}'
             default: 'os-odl_l2-nofeature-ha'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - description-setter:
     name: 'joid-os-nosdn-nofeature-ha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-nosdn-nofeature-ha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-nosdn-nofeature-ha-baremetal-colorado-trigger'
+    name: 'joid-os-nosdn-nofeature-ha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-nosdn-nofeature-ha trigger - branch: danube
+- trigger:
+    name: 'joid-os-nosdn-nofeature-ha-baremetal-danube-trigger'
     triggers:
         - timed: '0 2 * * *'
 - trigger:
-    name: 'joid-os-nosdn-nofeature-ha-virtual-colorado-trigger'
+    name: 'joid-os-nosdn-nofeature-ha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-nosdn-nofeature-ha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-nosdn-nofeature-ha-orange-pod1-colorado-trigger'
+    name: 'joid-os-nosdn-nofeature-ha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
 # os-odl_l2-nofeature-ha trigger - branch: master
     name: 'joid-os-odl_l2-nofeature-ha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-odl_l2-nofeature-ha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-odl_l2-nofeature-ha-baremetal-colorado-trigger'
+    name: 'joid-os-odl_l2-nofeature-ha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-odl_l2-nofeature-ha trigger - branch: danube
+- trigger:
+    name: 'joid-os-odl_l2-nofeature-ha-baremetal-danube-trigger'
     triggers:
         - timed: '0 7 * * *'
 - trigger:
-    name: 'joid-os-odl_l2-nofeature-ha-virtual-colorado-trigger'
+    name: 'joid-os-odl_l2-nofeature-ha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-odl_l2-nofeature-ha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-odl_l2-nofeature-ha-orange-pod1-colorado-trigger'
+    name: 'joid-os-odl_l2-nofeature-ha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
 # os-onos-nofeature-ha trigger - branch: master
     name: 'joid-os-onos-nofeature-ha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-onos-nofeature-ha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-onos-nofeature-ha-baremetal-colorado-trigger'
+    name: 'joid-os-onos-nofeature-ha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-onos-nofeature-ha trigger - branch: danube
+- trigger:
+    name: 'joid-os-onos-nofeature-ha-baremetal-danube-trigger'
     triggers:
         - timed: '0 12 * * *'
 - trigger:
-    name: 'joid-os-onos-nofeature-ha-virtual-colorado-trigger'
+    name: 'joid-os-onos-nofeature-ha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-onos-nofeature-ha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-onos-nofeature-ha-orange-pod1-colorado-trigger'
+    name: 'joid-os-onos-nofeature-ha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
 # os-onos-sfc-ha trigger - branch: master
     name: 'joid-os-onos-sfc-ha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-onos-sfc-ha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-onos-sfc-ha-baremetal-colorado-trigger'
+    name: 'joid-os-onos-sfc-ha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-onos-sfc-ha trigger - branch: danube
+- trigger:
+    name: 'joid-os-onos-sfc-ha-baremetal-danube-trigger'
     triggers:
         - timed: '0 17 * * *'
 - trigger:
-    name: 'joid-os-onos-sfc-ha-virtual-colorado-trigger'
+    name: 'joid-os-onos-sfc-ha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-onos-sfc-ha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-onos-sfc-ha-orange-pod1-colorado-trigger'
+    name: 'joid-os-onos-sfc-ha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
 # os-nosdn-lxd-noha trigger - branch: master
     name: 'joid-os-nosdn-lxd-noha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-nosdn-lxd-noha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-nosdn-lxd-noha-baremetal-colorado-trigger'
+    name: 'joid-os-nosdn-lxd-noha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-nosdn-lxd-noha trigger - branch: danube
+- trigger:
+    name: 'joid-os-nosdn-lxd-noha-baremetal-danube-trigger'
     triggers:
         - timed: '0 22 * * *'
 - trigger:
-    name: 'joid-os-nosdn-lxd-noha-virtual-colorado-trigger'
+    name: 'joid-os-nosdn-lxd-noha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-nosdn-lxd-noha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-nosdn-lxd-noha-orange-pod1-colorado-trigger'
+    name: 'joid-os-nosdn-lxd-noha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
 # os-nosdn-lxd-ha trigger - branch: master
     name: 'joid-os-nosdn-lxd-ha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-nosdn-lxd-ha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-nosdn-lxd-ha-baremetal-colorado-trigger'
+    name: 'joid-os-nosdn-lxd-ha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-nosdn-lxd-ha trigger - branch: danube
+- trigger:
+    name: 'joid-os-nosdn-lxd-ha-baremetal-danube-trigger'
     triggers:
         - timed: '0 10 * * *'
 - trigger:
-    name: 'joid-os-nosdn-lxd-ha-virtual-colorado-trigger'
+    name: 'joid-os-nosdn-lxd-ha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-nosdn-lxd-ha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-nosdn-lxd-ha-orange-pod1-colorado-trigger'
+    name: 'joid-os-nosdn-lxd-ha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
 # os-nosdn-nofeature-noha trigger - branch: master
     name: 'joid-os-nosdn-nofeature-noha-orange-pod1-master-trigger'
     triggers:
         - timed: ''
-# os-nosdn-nofeature-noha trigger - branch: colorado
 - trigger:
-    name: 'joid-os-nosdn-nofeature-noha-baremetal-colorado-trigger'
+    name: 'joid-os-nosdn-nofeature-noha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# os-nosdn-nofeature-noha trigger - branch: danube
+- trigger:
+    name: 'joid-os-nosdn-nofeature-noha-baremetal-danube-trigger'
     triggers:
         - timed: '0 4 * * *'
 - trigger:
-    name: 'joid-os-nosdn-nofeature-noha-virtual-colorado-trigger'
+    name: 'joid-os-nosdn-nofeature-noha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-nosdn-nofeature-noha-orange-pod1-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-os-nosdn-nofeature-noha-cengn-pod1-danube-trigger'
+    triggers:
+        - timed: ''
+# k8-nosdn-nofeature-noha trigger - branch: master
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-baremetal-master-trigger'
+    triggers:
+        - timed: '5 15 * * *'
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-virtual-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-orange-pod1-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# k8-nosdn-nofeature-noha trigger - branch: danube
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-baremetal-danube-trigger'
+    triggers:
+        - timed: '0 15 * * *'
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-orange-pod1-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-nofeature-noha-cengn-pod1-danube-trigger'
+    triggers:
+        - timed: ''
+# k8-nosdn-lb-noha trigger - branch: master
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-baremetal-master-trigger'
+    triggers:
+        - timed: '5 20 * * *'
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-virtual-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-orange-pod1-master-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-cengn-pod1-master-trigger'
+    triggers:
+        - timed: ''
+# k8-nosdn-lb-noha trigger - branch: danube
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-baremetal-danube-trigger'
+    triggers:
+        - timed: '0 20 * * *'
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-virtual-danube-trigger'
+    triggers:
+        - timed: ''
+- trigger:
+    name: 'joid-k8-nosdn-lb-noha-orange-pod1-danube-trigger'
     triggers:
         - timed: ''
 - trigger:
-    name: 'joid-os-nosdn-nofeature-noha-orange-pod1-colorado-trigger'
+    name: 'joid-k8-nosdn-lb-noha-cengn-pod1-danube-trigger'
     triggers:
         - timed: ''
index 05c2de1..e197dbd 100644 (file)
@@ -45,17 +45,24 @@ export POD_NAME=${POD/-}
 ##
 
 cd $WORKSPACE/ci
-if [ -e "$LAB_CONFIG/environments.yaml" ] && [ "$MAAS_REINSTALL" == "false" ]; then
+
+if [ -e "$LAB_CONFIG/deployconfig.yaml" ] && [ "$MAAS_REINSTALL" == "false" ]; then
     echo "------ Recover Juju environment to use MAAS ------"
-    cp $LAB_CONFIG/environments.yaml .
-    cp $LAB_CONFIG/deployment.yaml .
-    if [ -e $LAB_CONFIG/deployconfig.yaml ]; then
+    if [ ! -e deployconfig.yaml ]; then
         cp $LAB_CONFIG/deployconfig.yaml .
+        cp $LAB_CONFIG/deployment.yaml .
+        cp $LAB_CONFIG/labconfig.yaml .
     fi
 else
-    echo "------ Redeploy MAAS ------"
-    ./00-maasdeploy.sh $POD_NAME
-    exit_on_error $? "MAAS Deploy FAILED"
+    if ["$NODE_NAME" == "default" ]; then
+        echo "------ Redeploy MAAS ------"
+        ./03-maasdeploy.sh default
+        exit_on_error $? "MAAS Deploy FAILED"
+    else
+        echo "------ Redeploy MAAS ------"
+        ./03-maasdeploy.sh custom $LAB_CONFIG/labconfig.yaml
+        exit_on_error $? "MAAS Deploy FAILED"
+    fi
 fi
 
 ##
@@ -64,8 +71,9 @@ fi
 
 # Based on scenario naming we can get joid options
 # naming convention:
-#    os-<controller>-<nfvfeature>-<mode>[-<extrastuff>]
+#    <model>-<controller>-<nfvfeature>-<mode>[-<extrastuff>]
 # With parameters:
+#    model=(os|k8)
 #    controller=(nosdn|odl_l3|odl_l2|onos|ocl)
 #       No odl_l3 today
 #    nfvfeature=(kvm|ovs|dpdk|nofeature)
@@ -77,6 +85,7 @@ fi
 IFS='-' read -r -a DEPLOY_OPTIONS <<< "${DEPLOY_SCENARIO}--"
 #last -- need to avoid nounset error
 
+JOID_MODEL=${DEPLOY_OPTIONS[0]}
 SDN_CONTROLLER=${DEPLOY_OPTIONS[1]}
 NFV_FEATURES=${DEPLOY_OPTIONS[2]}
 HA_MODE=${DEPLOY_OPTIONS[3]}
@@ -103,49 +112,47 @@ fi
 ## Configure Joid deployment
 ##
 
-echo "------ Deploy with juju ------"
-echo "Execute: ./deploy.sh -t $HA_MODE -o $OS_RELEASE -s $SDN_CONTROLLER -l $POD_NAME -d $UBUNTU_DISTRO -f $NFV_FEATURES"
+if [ "$JOID_MODEL" == 'k8' ]; then
+  echo "------ Deploy with juju ------"
+  echo "Execute: ./deploy.sh -m $JOID_MODEL -s $SDN_CONTROLLER -l $POD_NAME -d $UBUNTU_DISTRO -f $NFV_FEATURES"
 
-./deploy.sh -t $HA_MODE -o $OS_RELEASE -s $SDN_CONTROLLER -l $POD_NAME -d $UBUNTU_DISTRO -f $NFV_FEATURES
-exit_on_error $? "Main deploy FAILED"
+  ./deploy.sh -m kubernetes -s $SDN_CONTROLLER -l $POD_NAME -d $UBUNTU_DISTRO -f $NFV_FEATURES
+  exit_on_error $? "Main deploy FAILED"
+fi
 
 ##
 ## Set Admin RC
 ##
-JOID_ADMIN_OPENRC=$LAB_CONFIG/admin-openrc
-echo "------ Create OpenRC file [$JOID_ADMIN_OPENRC] ------"
-
-# get controller IP
-case "$SDN_CONTROLLER" in
-    "odl")
-        SDN_CONTROLLER_IP=$(juju status odl-controller/0 |grep public-address|sed -- 's/.*\: //')
-        ;;
-    "onos")
-        SDN_CONTROLLER_IP=$(juju status onos-controller/0 |grep public-address|sed -- 's/.*\: //')
-        ;;
-    *)
-        SDN_CONTROLLER_IP='none'
-        ;;
-esac
-SDN_PASSWORD='admin'
-
-# export the openrc file by getting the one generated by joid and add SDN
-# controller for Functest
-cp ./cloud/admin-openrc $JOID_ADMIN_OPENRC
-cat << EOF >> $JOID_ADMIN_OPENRC
-export SDN_CONTROLLER=$SDN_CONTROLLER_IP
-export SDN_PASSWORD=$SDN_PASSWORD
-EOF
-
-##
-## Backup local juju env
-##
+if [ "$JOID_MODEL" == 'os' ]; then
+  echo "------ Deploy with juju ------"
+  echo "Execute: ./deploy.sh -m $JOID_MODEL -t $HA_MODE -o $OS_RELEASE -s $SDN_CONTROLLER -l $POD_NAME -d $UBUNTU_DISTRO -f $NFV_FEATURES"
+
+  ./deploy.sh -m openstack -t $HA_MODE -o $OS_RELEASE -s $SDN_CONTROLLER -l $POD_NAME -d $UBUNTU_DISTRO -f $NFV_FEATURES
+  exit_on_error $? "Main deploy FAILED"
+
+  JOID_ADMIN_OPENRC=$LAB_CONFIG/admin-openrc
+  echo "------ Create OpenRC file [$JOID_ADMIN_OPENRC] ------"
+
+  # get controller IP
+  case "$SDN_CONTROLLER" in
+      "odl")
+          SDN_CONTROLLER_IP=$(juju status odl-controller/0 |grep public-address|sed -- 's/.*\: //')
+          ;;
+      "onos")
+          SDN_CONTROLLER_IP=$(juju status onos-controller/0 |grep public-address|sed -- 's/.*\: //')
+          ;;
+      *)
+          SDN_CONTROLLER_IP='none'
+          ;;
+  esac
+  SDN_PASSWORD='admin'
+
+  # export the openrc file by getting the one generated by joid and add SDN
+  # controller for Functest
+  # cp ./cloud/admin-openrc $JOID_ADMIN_OPENRC
+  echo export SDN_CONTROLLER=$SDN_CONTROLLER_IP >> $JOID_ADMIN_OPENRC
+  echo export SDN_PASSWORD=$SDN_PASSWORD >> $JOID_ADMIN_OPENRC
 
-echo "------ Backup Juju environment ------"
-cp environments.yaml $LAB_CONFIG/
-cp deployment.yaml $LAB_CONFIG/
-if [ -e deployconfig.yaml ]; then
-    cp deployconfig.yaml $LAB_CONFIG
 fi
 
 ##
index 9d362d8..03fab55 100644 (file)
@@ -12,7 +12,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
@@ -45,6 +45,7 @@
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             use-build-blocker: true
             blocking-jobs:
                 - 'joid-verify-master'
-                - 'joid-verify-colorado'
+                - 'joid-verify-danube'
             block-level: 'NODE'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -89,6 +86,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
@@ -97,7 +95,6 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'joid-virtual-defaults'
 
                 - name: 'joid-verify-basic-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'joid-verify-deploy-virtual-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
                 - name: 'joid-verify-smoke-test-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                     GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 4
             block-level: 'NODE'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults'
index 06377ac..b31d61c 100755 (executable)
@@ -13,13 +13,20 @@ else
     exit 1
 fi
 
+echo $TEST_NAME
+
 # do stuff differently based on the job type
 case "$JOB_TYPE" in
-    verify|daily)
+    verify)
         #start the test
         cd $WORKSPACE
         ./ci/test_kvmfornfv.sh $JOB_TYPE
         ;;
+    daily)
+        #start the test
+        cd $WORKSPACE
+        ./ci/test_kvmfornfv.sh $JOB_TYPE $TEST_NAME
+        ;;
     *)
         echo "Test is not enabled for $JOB_TYPE jobs"
         exit 1
index 6f8fff3..56fb4f9 100755 (executable)
@@ -11,16 +11,17 @@ fi
 
 case "$JOB_TYPE" in
     verify)
-        OPNFV_ARTIFACT_VERSION="gerrit-$GERRIT_CHANGE_NUMBER"
-        GS_UPLOAD_LOCATION="gs://artifacts.opnfv.org/$PROJECT/review/$GERRIT_CHANGE_NUMBER"
-        echo "Removing outdated artifacts produced for the previous patch for the change $GERRIT_CHANGE_NUMBER"
-        gsutil ls $GS_UPLOAD_LOCATION > /dev/null 2>&1 && gsutil rm -r $GS_UPLOAD_LOCATION
-        echo "Uploading artifacts for the change $GERRIT_CHANGE_NUMBER. This could take some time..."
-        ;;
+       OPNFV_ARTIFACT_VERSION="gerrit-$GERRIT_CHANGE_NUMBER"
+       GS_UPLOAD_LOCATION="gs://artifacts.opnfv.org/$PROJECT/review/$GERRIT_CHANGE_NUMBER"
+       echo "Removing outdated artifacts produced for the previous patch for the change $GERRIT_CHANGE_NUMBER"
+       gsutil ls $GS_UPLOAD_LOCATION > /dev/null 2>&1 && gsutil rm -r $GS_UPLOAD_LOCATION
+       echo "Uploading artifacts for the change $GERRIT_CHANGE_NUMBER. This could take some time..."
+       ;;
     daily)
         echo "Uploading daily artifacts This could take some time..."
         OPNFV_ARTIFACT_VERSION=$(date -u +"%Y-%m-%d_%H-%M-%S")
         GS_UPLOAD_LOCATION="gs://$GS_URL/$OPNFV_ARTIFACT_VERSION"
+        GS_LOG_LOCATION="gs://$GS_URL/logs-$(date -u +"%Y-%m-%d")"/
         ;;
     *)
         echo "Artifact upload is not enabled for $JOB_TYPE jobs"
@@ -38,10 +39,23 @@ esac
 source $WORKSPACE/opnfv.properties
 
 # upload artifacts
-gsutil cp -r $WORKSPACE/build_output/* $GS_UPLOAD_LOCATION > $WORKSPACE/gsutil.log 2>&1
-gsutil -m setmeta -r \
-    -h "Cache-Control:private, max-age=0, no-transform" \
-    $GS_UPLOAD_LOCATION > /dev/null 2>&1
+if [[ "$PHASE" == "build" ]]; then
+    gsutil cp -r $WORKSPACE/build_output/* $GS_UPLOAD_LOCATION > $WORKSPACE/gsutil.log 2>&1
+    gsutil -m setmeta -r \
+        -h "Cache-Control:private, max-age=0, no-transform" \
+        $GS_UPLOAD_LOCATION > /dev/null 2>&1
+else
+    if [[ "$JOB_TYPE" == "daily" ]]; then
+        log_dir=$WORKSPACE/build_output/log
+        if [[ -d "$log_dir" ]]; then
+            #Uploading logs to artifacts
+            echo "Uploading artifacts for future debugging needs...."
+            gsutil cp -r $WORKSPACE/build_output/log-*.tar.gz $GS_LOG_LOCATION > $WORKSPACE/gsutil.log 2>&1
+        else
+            echo "No test logs/artifacts available for uploading"
+        fi
+    fi
+fi
 
 # upload metadata file for the artifacts built by daily job
 if [[ "$JOB_TYPE" == "daily" ]]; then
index b6a55fe..8d607f9 100644 (file)
@@ -8,7 +8,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
         - 'build':
             slave-label: 'opnfv-build-ubuntu'
         - 'test':
-            slave-label: 'intel-pod1'
+            slave-label: 'intel-pod10'
+#####################################
+# patch verification phases
+#####################################
+    testname:
+        - 'cyclictest'
+        - 'packet_forward'
 #####################################
 # patch verification phases
 #####################################
@@ -28,7 +34,7 @@
         - 'kvmfornfv-verify-{phase}-{stream}'
         - 'kvmfornfv-merge-{stream}'
         - 'kvmfornfv-daily-{stream}'
-        - 'kvmfornfv-daily-{phase}-{stream}'
+        - 'kvmfornfv-{testname}-daily-{phase}-{stream}'
 #####################################
 # job templates
 #####################################
@@ -42,6 +48,7 @@
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-total: 3
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**'
 
     builders:
         - description-setter:
@@ -83,7 +94,7 @@
                 - name: 'kvmfornfv-verify-build-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                   node-parameters: false
                 - name: 'kvmfornfv-verify-test-{stream}'
                   current-parameters: false
                   predefined-parameters: |
-                    GERRIT_BRANCH=$GERRIT_BRANCH
+                    BRANCH=$BRANCH
                     GERRIT_REFSPEC=$GERRIT_REFSPEC
                     GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER
                   node-parameters: false
                   kill-phase-on: FAILURE
                   abort-all-job: true
-
 - job-template:
     name: 'kvmfornfv-verify-{phase}-{stream}'
 
     concurrent: true
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
         - 'kvmfornfv-defaults':
             gs-pathname: '{gs-pathname}'
+        - string:
+            name: PHASE
+            default: '{phase}'
+            description: "Execution of kvmfornfv daily '{phase}' job ."
 
     builders:
         - description-setter:
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - 'kvmfornfv-defaults':
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - 'kvmfornfv-defaults':
             gs-pathname: '{gs-pathname}'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
-         - timed: '@midnight'
+        - timed: '@midnight'
 
     builders:
         - description-setter:
             description: "Built on $NODE_NAME"
         - multijob:
-            name: build
+            name: cyclictest-build
             condition: SUCCESSFUL
             projects:
-                - name: 'kvmfornfv-daily-build-{stream}'
+                - name: 'kvmfornfv-cyclictest-daily-build-{stream}'
                   current-parameters: false
                   node-parameters: false
                   git-revision: true
                   kill-phase-on: FAILURE
                   abort-all-job: true
         - multijob:
-            name: test
+            name: cyclictest-test
             condition: SUCCESSFUL
             projects:
-                - name: 'kvmfornfv-daily-test-{stream}'
+                - name: 'kvmfornfv-cyclictest-daily-test-{stream}'
+                  current-parameters: false
+                  node-parameters: false
+                  git-revision: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: packetforward-build
+            condition: SUCCESSFUL
+            projects:
+                - name: 'kvmfornfv-packet_forward-daily-build-{stream}'
+                  current-parameters: false
+                  node-parameters: false
+                  git-revision: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: packetforward-test
+            condition: SUCCESSFUL
+            projects:
+                - name: 'kvmfornfv-packet_forward-daily-test-{stream}'
                   current-parameters: false
                   node-parameters: false
                   git-revision: true
                   kill-phase-on: FAILURE
                   abort-all-job: true
-
 
 - job-template:
-    name: 'kvmfornfv-daily-{phase}-{stream}'
+    name: 'kvmfornfv-{testname}-daily-{phase}-{stream}'
 
     disabled: '{obj:disabled}'
 
     concurrent: false
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
+        - ssh-agent-wrapper
         - timeout:
             timeout: 360
             fail: true
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
         - 'kvmfornfv-defaults':
             gs-pathname: '{gs-pathname}'
+        - string:
+            name: TEST_NAME
+            default: '{testname}'
+            description: "Daily job to execute kvmfornfv '{testname}' testcase."
+        - string:
+            name: PHASE
+            default: '{phase}'
+            description: "Execution of kvmfornfv daily '{phase}' job ."
 
     builders:
         - description-setter:
             description: "Built on $NODE_NAME"
-        - '{project}-daily-{phase}-macro'
+        - '{project}-{testname}-daily-{phase}-macro'
 #####################################
 # builder macros
 #####################################
         - shell:
             !include-raw: ./kvmfornfv-test.sh
 - builder:
-    name: 'kvmfornfv-daily-build-macro'
+    name: 'kvmfornfv-cyclictest-daily-build-macro'
     builders:
         - shell:
             !include-raw: ./kvmfornfv-build.sh
         - shell:
             !include-raw: ./kvmfornfv-upload-artifact.sh
 - builder:
-    name: 'kvmfornfv-daily-test-macro'
+    name: 'kvmfornfv-cyclictest-daily-test-macro'
+    builders:
+        - shell:
+            !include-raw: ./kvmfornfv-download-artifact.sh
+        - shell:
+            !include-raw: ./kvmfornfv-test.sh
+        - shell:
+            !include-raw: ./kvmfornfv-upload-artifact.sh
+- builder:
+    name: 'kvmfornfv-packet_forward-daily-build-macro'
+    builders:
+        - shell:
+            !include-raw: ./kvmfornfv-build.sh
+        - shell:
+            !include-raw: ./kvmfornfv-upload-artifact.sh
+- builder:
+    name: 'kvmfornfv-packet_forward-daily-test-macro'
     builders:
         - shell:
             !include-raw: ./kvmfornfv-download-artifact.sh
         - shell:
             !include-raw: ./kvmfornfv-test.sh
-
 #####################################
 # parameter macros
 #####################################
diff --git a/jjb/models/models.yml b/jjb/models/models.yml
new file mode 100644 (file)
index 0000000..6831036
--- /dev/null
@@ -0,0 +1,68 @@
+###################################################
+# All the jobs except verify have been removed!
+# They will only be enabled on request by projects!
+###################################################
+- project:
+    name: models
+
+    project: '{name}'
+
+    jobs:
+        - 'models-verify-{stream}'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+
+- job-template:
+    name: 'models-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**|.gitignore'
+
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o nounset
+            set -o pipefail
+
+            # shellcheck -f tty tests/*.sh
index 55d593f..fb28feb 100644 (file)
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -45,6 +42,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
@@ -53,4 +51,4 @@
         - shell:
             #!/bin/bash
             echo "launch Moon unit tests"
-            nosetest $WORKSPACE/keystone-moon/keystone/tests/moon/unit
\ No newline at end of file
+            nosetest $WORKSPACE/keystone-moon/keystone/tests/moon/unit
diff --git a/jjb/multisite/fuel-deploy-for-multisite.sh b/jjb/multisite/fuel-deploy-for-multisite.sh
new file mode 100755 (executable)
index 0000000..71c6cc1
--- /dev/null
@@ -0,0 +1,124 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o nounset
+set -o pipefail
+
+# do not continue with the deployment if FRESH_INSTALL is not requested
+if [[ "$FRESH_INSTALL" == "true" ]]; then
+    echo "Fresh install requested. Proceeding with the installation."
+else
+    echo "Fresh install is not requested. Skipping the installation."
+    exit 0
+fi
+
+export TERM="vt220"
+export BRANCH=$(echo $BRANCH | sed 's/stable\///g')
+# get the latest successful job console log and extract the properties filename
+FUEL_DEPLOY_BUILD_URL="https://build.opnfv.org/ci/job/fuel-deploy-virtual-daily-$BRANCH/lastSuccessfulBuild/consoleText"
+FUEL_PROPERTIES_FILE=$(curl -s -L ${FUEL_DEPLOY_BUILD_URL} | grep 'ISO:' | awk '{print $2}' | sed 's/iso/properties/g')
+if [[ -z "FUEL_PROPERTIES_FILE" ]]; then
+    echo "Unable to extract the url to Fuel ISO properties from ${FUEL_DEPLOY_URL}"
+    exit 1
+fi
+
+# use known/working version of fuel
+#FUEL_PROPERTIES_FILE="opnfv-2017-03-06_16-00-15.properties"
+curl -L -s -o $WORKSPACE/latest.properties $GS_PATH/$FUEL_PROPERTIES_FILE
+
+# source the file so we get OPNFV vars
+source latest.properties
+
+# echo the info about artifact that is used during the deployment
+echo "Using ${OPNFV_ARTIFACT_URL/*\/} for deployment"
+
+# download the iso
+echo "Downloading the ISO using the link http://$OPNFV_ARTIFACT_URL"
+curl -L -s -o $WORKSPACE/opnfv.iso http://$OPNFV_ARTIFACT_URL > gsutil.iso.log 2>&1
+
+
+# set deployment parameters
+DEPLOY_SCENARIO="os-nosdn-nofeature-noha"
+export TMPDIR=$HOME/tmpdir
+BRIDGE=${BRIDGE:-pxebr}
+LAB_NAME=${NODE_NAME/-*}
+POD_NAME=${NODE_NAME/*-}
+
+if [[ "$NODE_NAME" =~ "virtual" ]]; then
+    POD_NAME="virtual_kvm"
+fi
+
+# we currently support ericsson, intel, lf and zte labs
+if [[ ! "$LAB_NAME" =~ (ericsson|intel|lf|zte) ]]; then
+    echo "Unsupported/unidentified lab $LAB_NAME. Cannot continue!"
+    exit 1
+else
+    echo "Using configuration for $LAB_NAME"
+fi
+
+# create TMPDIR if it doesn't exist
+export TMPDIR=$HOME/tmpdir
+mkdir -p $TMPDIR
+
+# change permissions down to TMPDIR
+chmod a+x $HOME
+chmod a+x $TMPDIR
+
+# clone fuel repo and checkout the sha1 that corresponds to the ISO
+echo "Cloning fuel repo"
+git clone https://gerrit.opnfv.org/gerrit/p/fuel.git fuel
+cd $WORKSPACE/fuel
+echo "Checking out $OPNFV_GIT_SHA1"
+git checkout $OPNFV_GIT_SHA1 --quiet
+
+# clone the securedlab repo
+cd $WORKSPACE
+echo "Cloning securedlab repo ${GIT_BRANCH##origin/}"
+git clone ssh://jenkins-ericsson@gerrit.opnfv.org:29418/securedlab --quiet \
+    --branch ${GIT_BRANCH##origin/}
+
+# log file name
+FUEL_LOG_FILENAME="${JOB_NAME}_${BUILD_NUMBER}.log.tar.gz"
+
+# construct the command
+DEPLOY_COMMAND="sudo $WORKSPACE/fuel/ci/deploy.sh -b file://$WORKSPACE/securedlab \
+    -l $LAB_NAME -p $POD_NAME -s $DEPLOY_SCENARIO -i file://$WORKSPACE/opnfv.iso \
+    -H -B $BRIDGE -S $TMPDIR -L $WORKSPACE/$FUEL_LOG_FILENAME"
+
+# log info to console
+echo "Deployment parameters"
+echo "--------------------------------------------------------"
+echo "Scenario: $DEPLOY_SCENARIO"
+echo "Lab: $LAB_NAME"
+echo "POD: $POD_NAME"
+echo "ISO: ${OPNFV_ARTIFACT_URL/*\/}"
+echo
+echo "Starting the deployment using $INSTALLER_TYPE. This could take some time..."
+echo "--------------------------------------------------------"
+echo
+
+# start the deployment
+echo "Issuing command"
+echo "$DEPLOY_COMMAND"
+echo
+
+$DEPLOY_COMMAND
+exit_code=$?
+
+echo
+echo "--------------------------------------------------------"
+echo "Deployment is done!"
+
+if [[ $exit_code -ne 0 ]]; then
+    echo "Deployment failed!"
+    exit $exit_code
+else
+    echo "Deployment is successful!"
+    exit 0
+fi
diff --git a/jjb/multisite/multisite-daily-jobs.yml b/jjb/multisite/multisite-daily-jobs.yml
new file mode 100644 (file)
index 0000000..06cefb6
--- /dev/null
@@ -0,0 +1,305 @@
+- project:
+    name: kingbird
+
+    project: 'multisite'
+
+    jobs:
+        - 'multisite-kingbird-virtual-daily-{stream}'
+        - 'multisite-{phase}-{stream}'
+
+    phase:
+        - 'fuel-deploy-regionone-virtual':
+            slave-label: ericsson-virtual12
+        - 'fuel-deploy-regiontwo-virtual':
+            slave-label: ericsson-virtual13
+        - 'register-endpoints':
+            slave-label: ericsson-virtual12
+        - 'update-auth':
+            slave-label: ericsson-virtual13
+        - 'kingbird-deploy-virtual':
+            slave-label: ericsson-virtual12
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+            timed: '0 12 * * *'
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+            timed: '0 0 * * *'
+
+- job-template:
+    name: 'multisite-kingbird-virtual-daily-{stream}'
+
+    project-type: multijob
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - choice:
+            name: FRESH_INSTALL
+            choices:
+                - 'true'
+                - 'false'
+        - string:
+            name: KINGBIRD_LOG_FILE
+            default: $WORKSPACE/kingbird.log
+        - 'opnfv-build-defaults'
+
+    triggers:
+         - timed: '{timed}'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: fuel-deploy-virtual
+            condition: SUCCESSFUL
+            projects:
+                - name: 'multisite-fuel-deploy-regionone-virtual-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    FUEL_VERSION=latest
+                    DEPLOY_SCENARIO=os-nosdn-nofeature-noha
+                    OS_REGION=RegionOne
+                    REGIONONE_IP=100.64.209.10
+                    REGIONTWO_IP=100.64.209.11
+                    FRESH_INSTALL=$FRESH_INSTALL
+                  node-parameters: false
+                  node-label-name: SLAVE_LABEL
+                  node-label: ericsson-virtual12
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+                - name: 'multisite-fuel-deploy-regiontwo-virtual-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    FUEL_VERSION=latest
+                    DEPLOY_SCENARIO=os-nosdn-nofeature-noha
+                    OS_REGION=RegionTwo
+                    REGIONONE_IP=100.64.209.10
+                    REGIONTWO_IP=100.64.209.11
+                    FRESH_INSTALL=$FRESH_INSTALL
+                  node-parameters: false
+                  node-label-name: SLAVE_LABEL
+                  node-label: ericsson-virtual13
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: centralize-keystone
+            condition: SUCCESSFUL
+            projects:
+                - name: 'multisite-register-endpoints-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    OS_REGION=RegionOne
+                    REGIONONE_IP=100.64.209.10
+                    REGIONTWO_IP=100.64.209.11
+                    FRESH_INSTALL=$FRESH_INSTALL
+                  node-parameters: false
+                  node-label-name: SLAVE_LABEL
+                  node-label: ericsson-virtual12
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+                - name: 'multisite-update-auth-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    OS_REGION=RegionTwo
+                    REGIONONE_IP=100.64.209.10
+                    REGIONTWO_IP=100.64.209.11
+                    FRESH_INSTALL=$FRESH_INSTALL
+                  node-parameters: false
+                  node-label-name: SLAVE_LABEL
+                  node-label: ericsson-virtual13
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: kingbird-deploy-virtual
+            condition: SUCCESSFUL
+            projects:
+                - name: 'multisite-kingbird-deploy-virtual-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    OS_REGION=RegionOne
+                    REGIONONE_IP=100.64.209.10
+                    REGIONTWO_IP=100.64.209.11
+                    FRESH_INSTALL=$FRESH_INSTALL
+                  node-parameters: false
+                  node-label-name: SLAVE_LABEL
+                  node-label: ericsson-virtual12
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: kingbird-functest
+            condition: SUCCESSFUL
+            projects:
+                - name: 'functest-fuel-virtual-suite-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    DEPLOY_SCENARIO=os-nosdn-multisite-noha
+                    FUNCTEST_SUITE_NAME=multisite
+                    OS_REGION=RegionOne
+                    REGIONONE_IP=100.64.209.10
+                    REGIONTWO_IP=100.64.209.11
+                    FRESH_INSTALL=$FRESH_INSTALL
+                  node-parameters: false
+                  node-label-name: SLAVE_LABEL
+                  node-label: ericsson-virtual12
+                  kill-phase-on: NEVER
+                  abort-all-job: false
+
+- job-template:
+    name: 'multisite-{phase}-{stream}'
+
+    concurrent: false
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - string:
+            name: KINGBIRD_LOG_FILE
+            default: $WORKSPACE/kingbird.log
+        - string:
+            name: GS_PATH
+            default: 'http://artifacts.opnfv.org/fuel{gs-pathname}'
+        - 'fuel-defaults'
+        - '{slave-label}-defaults'
+        - choice:
+            name: FRESH_INSTALL
+            choices:
+                - 'true'
+                - 'false'
+
+    scm:
+        - git-scm
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - 'multisite-{phase}-builder':
+            stream: '{stream}'
+
+    publishers:
+        - 'multisite-{phase}-publisher'
+
+########################
+# builder macros
+########################
+- builder:
+    name: 'multisite-fuel-deploy-regionone-virtual-builder'
+    builders:
+        - shell:
+            !include-raw-escape: ./fuel-deploy-for-multisite.sh
+        - shell: |
+            #!/bin/bash
+
+            echo "This is where we deploy fuel, extract passwords and save into file"
+
+            cd $WORKSPACE/tools/keystone/
+            ./run.sh -t controller -r fetchpass.sh -o servicepass.ini
+
+- builder:
+    name: 'multisite-fuel-deploy-regiontwo-virtual-builder'
+    builders:
+        - shell:
+            !include-raw-escape: ./fuel-deploy-for-multisite.sh
+        - shell: |
+            #!/bin/bash
+
+            echo "This is where we deploy fuel, extract publicUrl, privateUrl, and adminUrl and save into file"
+
+            cd $WORKSPACE/tools/keystone/
+            ./run.sh -t controller -r endpoint.sh -o endpoints.ini
+- builder:
+    name: 'multisite-register-endpoints-builder'
+    builders:
+        - copyartifact:
+            project: 'multisite-fuel-deploy-regiontwo-virtual-{stream}'
+            which-build: multijob-build
+            filter: "endpoints.ini"
+        - shell: |
+            #!/bin/bash
+
+            echo "This is where we register RegionTwo in RegionOne keystone using endpoints.ini"
+
+            cd $WORKSPACE/tools/keystone/
+            ./run.sh -t controller -r region.sh -d $WORKSPACE/endpoints.ini
+- builder:
+    name: 'multisite-update-auth-builder'
+    builders:
+        - copyartifact:
+            project: 'multisite-fuel-deploy-regionone-virtual-{stream}'
+            which-build: multijob-build
+            filter: "servicepass.ini"
+        - shell: |
+            #!/bin/bash
+
+            echo "This is where we read passwords from servicepass.ini and replace passwords in RegionTwo"
+
+            cd $WORKSPACE/tools/keystone/
+            ./run.sh -t controller -r writepass.sh -d $WORKSPACE/servicepass.ini
+            ./run.sh -t compute -r writepass.sh -d $WORKSPACE/servicepass.ini
+- builder:
+    name: 'multisite-kingbird-deploy-virtual-builder'
+    builders:
+        - shell: |
+            #!/bin/bash
+
+            echo "This is where we install kingbird"
+            cd $WORKSPACE/tools/kingbird
+            ./deploy.sh
+########################
+# publisher macros
+########################
+- publisher:
+    name: 'multisite-fuel-deploy-regionone-virtual-publisher'
+    publishers:
+        - archive:
+            artifacts: 'servicepass.ini'
+            allow-empty: false
+            only-if-success: true
+            fingerprint: true
+- publisher:
+    name: 'multisite-fuel-deploy-regiontwo-virtual-publisher'
+    publishers:
+        - archive:
+            artifacts: 'endpoints.ini'
+            allow-empty: false
+            only-if-success: true
+            fingerprint: true
+- publisher:
+    name: 'multisite-register-endpoints-publisher'
+    publishers:
+        - archive:
+            artifacts: 'dummy.txt'
+            allow-empty: true
+- publisher:
+    name: 'multisite-update-auth-publisher'
+    publishers:
+        - archive:
+            artifacts: 'dummy.txt'
+            allow-empty: true
+- publisher:
+    name: 'multisite-kingbird-deploy-virtual-publisher'
+    publishers:
+        - archive:
+            artifacts: 'dummy.txt'
+            allow-empty: true
+- publisher:
+    name: 'multisite-kingbird-functest-publisher'
+    publishers:
+        - archive:
+            artifacts: 'dummy.txt'
+            allow-empty: true
diff --git a/jjb/multisite/multisite-verify-jobs.yml b/jjb/multisite/multisite-verify-jobs.yml
new file mode 100644 (file)
index 0000000..9431e0b
--- /dev/null
@@ -0,0 +1,69 @@
+###################################################
+# All the jobs except verify have been removed!
+# They will only be enabled on request by projects!
+###################################################
+- project:
+    name: multisite
+
+    project: '{name}'
+
+    jobs:
+        - 'multisite-verify-{stream}'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+            timed: '@midnight'
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+            timed: ''
+
+- job-template:
+    name: 'multisite-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**|.gitignore'
+
+    builders:
+        - shell: |
+            #!/bin/bash
+
+            echo "Hello World"
diff --git a/jjb/multisite/multisite.yml b/jjb/multisite/multisite.yml
deleted file mode 100644 (file)
index 24c03fd..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-###################################################
-# All the jobs except verify have been removed!
-# They will only be enabled on request by projects!
-###################################################
-- project:
-    name: multisite
-
-    project: '{name}'
-
-    jobs:
-        - 'multisite-verify-{stream}'
-        - 'multisite-kingbird-daily-{stream}'
-        - 'multisite-kingbird-deploy-{stream}'
-
-    stream:
-        - master:
-            branch: '{stream}'
-            gs-pathname: ''
-            disabled: false
-            timed: '@midnight'
-        - colorado:
-            branch: 'stable/{stream}'
-            gs-pathname: '/{stream}'
-            disabled: false
-            timed: ''
-
-- job-template:
-    name: 'multisite-verify-{stream}'
-
-    disabled: '{obj:disabled}'
-
-    concurrent: true
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-        - gerrit-parameter:
-            branch: '{branch}'
-        - 'opnfv-build-ubuntu-defaults'
-
-    scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
-
-    triggers:
-        - gerrit:
-            trigger-on:
-                - patchset-created-event:
-                    exclude-drafts: 'false'
-                    exclude-trivial-rebase: 'false'
-                    exclude-no-code-change: 'false'
-                - draft-published-event
-                - comment-added-contains-event:
-                    comment-contains-value: 'recheck'
-                - comment-added-contains-event:
-                    comment-contains-value: 'reverify'
-            projects:
-              - project-compare-type: 'ANT'
-                project-pattern: '{project}'
-                branches:
-                  - branch-compare-type: 'ANT'
-                    branch-pattern: '**/{branch}'
-                forbidden-file-paths:
-                  - compare-type: ANT
-                    pattern: 'docs/**|.gitignore'
-
-    builders:
-        - shell: |
-            #!/bin/bash
-
-            echo "Hello World"
-
-- job-template:
-    name: 'multisite-kingbird-daily-{stream}'
-
-    project-type: freestyle
-
-    disabled: '{obj:disabled}'
-
-    concurrent: false
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-        - gerrit-parameter:
-            branch: '{branch}'
-        - string:
-            name: KINGBIRD_LOG_FILE
-            default: $WORKSPACE/kingbird.log
-        - 'intel-virtual6-defaults'
-        - string:
-            name: DEPLOY_SCENARIO
-            default: 'os-nosdn-multisite-ha'
-
-    scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
-
-    triggers:
-         - timed: '{timed}'
-
-    builders:
-        - trigger-builds:
-            - project: 'multisite-kingbird-deploy-{stream}'
-              current-parameters: true
-              same-node: true
-              block: true
-        - trigger-builds:
-            - project: 'functest-fuel-virtual-suite-{stream}'
-              current-parameters: true
-              predefined-parameters:
-                FUNCTEST_SUITE_NAME=multisite
-              same-node: true
-              block: true
-              block-thresholds:
-                build-step-failure-threshold: 'never'
-                failure-threshold: 'never'
-                unstable-threshold: 'FAILURE'
-
-- job-template:
-    name: 'multisite-kingbird-deploy-{stream}'
-
-    concurrent: false
-
-    scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'gerrit'
-
-    builders:
-        - 'multisite-kingbird-deploy'
-        - 'multisite-kingbird-log-upload'
-
-########################
-# builder macros
-########################
-- builder:
-    name: 'multisite-kingbird-deploy'
-    builders:
-        - shell: |
-            #!/bin/bash
-
-            $WORKSPACE/tools/kingbird/deploy.sh
-- builder:
-    name: 'multisite-kingbird-log-upload'
-    builders:
-        - shell: |
-            #!/bin/bash
-
-            echo "Here is where we upload kingbird logs to artifact repo"
-            echo "We just check the existence of log file"
-            ls -al $KINGBIRD_LOG_FILE
diff --git a/jjb/netready/netready-gluon-build.sh b/jjb/netready/netready-gluon-build.sh
new file mode 100755 (executable)
index 0000000..141e84c
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+echo "Building Gluon packages."
+echo "------------------------"
+echo
+
+OPNFV_ARTIFACT_VERSION=$(echo $(date -u +"%Y%m%d"))
+
+# build all packages
+cd $WORKSPACE/ci
+./build-gluon-packages.sh
+
+# list the contents of BUILD_OUTPUT directory
+echo "Build Directory is ${BUILD_DIRECTORY}"
+echo "Build Directory Contents:"
+echo "---------------------------------------"
+ls -alR $BUILD_DIRECTORY
+
+# get version infos from Gluon from spec
+GLUON_VERSION=$(grep Version: $BUILD_DIRECTORY/rpm_specs/gluon.spec | awk '{ print $2 }')
+GLUON_RELEASE=$(grep 'define release' $BUILD_DIRECTORY/rpm_specs/gluon.spec | awk '{ print $3 }')_$OPNFV_ARTIFACT_VERSION
+
+ARTIFACT_NAME=gluon-$GLUON_VERSION-$GLUON_RELEASE.noarch.rpm
+ARTIFACT_PATH=$BUILD_DIRECTORY/noarch/$ARTIFACT_NAME
+
+echo "Writing opnfv.properties file"
+# save information regarding artifact into file
+(
+  echo "OPNFV_ARTIFACT_VERSION=$OPNFV_ARTIFACT_VERSION"
+  echo "OPNFV_GIT_URL=$(git config --get remote.origin.url)"
+  echo "OPNFV_GIT_SHA1=$(git rev-parse HEAD)"
+  echo "OPNFV_ARTIFACT_URL=$GS_URL/$ARTIFACT_NAME"
+  echo "OPNFV_ARTIFACT_SHA512SUM=$(sha512sum $ARTIFACT_PATH | cut -d' ' -f1)"
+  echo "OPNFV_BUILD_URL=$BUILD_URL"
+  echo "ARTIFACT_LIST=$ARTIFACT_PATH"
+) > $WORKSPACE/opnfv.properties
+
+echo "---------------------------------------"
+echo "Done!"
diff --git a/jjb/netready/netready-upload-gluon-packages.sh b/jjb/netready/netready-upload-gluon-packages.sh
new file mode 100755 (executable)
index 0000000..7c1e337
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+echo "Uploading Gluon packages"
+echo "--------------------------------------------------------"
+echo
+
+source $WORKSPACE/opnfv.properties
+
+for artifact in $ARTIFACT_LIST; do
+  echo "Uploading artifact: ${artifact}"
+  gsutil cp $artifact gs://$GS_URL/$(basename $artifact) > gsutil.$(basename $artifact).log
+  echo "Upload complete for ${artifact}"
+done
+
+gsutil cp $WORKSPACE/opnfv.properties gs://$GS_URL/opnfv-$OPNFV_ARTIFACT_VERSION.properties > gsutil.properties.log
+gsutil cp $WORKSPACE/opnfv.properties gs://$GS_URL/latest.properties > gsutil.properties.log
+
+echo "--------------------------------------------------------"
+echo "Upload done!"
+
+echo "Artifacts are not available as:"
+for artifact in $ARTIFACT_LIST; do
+  echo "http://$GS_URL/$(basename $artifact)"
+done
index 3e2f95a..9a4d885 100644 (file)
@@ -5,16 +5,13 @@
 
     jobs:
         - 'netready-verify-{stream}'
+        - 'netready-build-gluon-packages-daily-{stream}'
 
     stream:
         - master:
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
-            branch: 'stable/{stream}'
-            gs-pathname: '/{stream}'
-            disabled: false
 
 - job-template:
     name: 'netready-verify-{stream}'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -50,6 +44,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
     builders:
         - shell: |
             echo "Nothing to verify!"
+
+
+
+- job-template:
+    name: 'netready-build-gluon-packages-daily-{stream}'
+
+    disabled: false
+
+    concurrent: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+        - 'netready-parameter':
+            gs-pathname: '{gs-pathname}'
+
+    scm:
+        - git-scm
+
+    builders:
+        - 'netready-gluon-build'
+
+    triggers:
+        - timed: '@midnight'
+
+
+########################
+# builder macros
+########################
+
+- builder:
+    name: 'netready-gluon-build'
+    builders:
+        - shell:
+            !include-raw: ./netready-gluon-build.sh
+        - shell:
+            !include-raw: ./netready-upload-gluon-packages.sh
+
+
+########################
+# parameter macros
+########################
+
+- parameter:
+    name: netready-parameter
+    parameters:
+        - string:
+            name: BUILD_DIRECTORY
+            default: $WORKSPACE/build
+            description: "Directory where the build artifact will be located upon the completion of the build."
+        - string:
+            name: GS_URL
+            default: artifacts.opnfv.org/$PROJECT{gs-pathname}
+            description: "URL to Google Storage."
index c4e34ca..c06fa89 100644 (file)
@@ -14,7 +14,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -55,6 +52,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index 0c90c57..9d6b037 100644 (file)
@@ -13,7 +13,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -59,6 +56,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - timed: '@midnight'
@@ -99,6 +95,7 @@
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - string:
             name: GS_URL
             description: "Directory where the build artifact will be located upon the completion     of the build."
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - 'builder-onosfw-helloworld'
similarity index 57%
rename from jjb/opnfv/opnfv-lint.yml
rename to jjb/openretriever/openretriever-project.yml
index f90f95d..3bcfab6 100644 (file)
@@ -1,48 +1,42 @@
-########################
-# Job configuration for opnfv-lint
-########################
+###################################################
+# All the jobs except verify have been removed!
+# They will only be enabled on request by projects!
+###################################################
 - project:
+    name: openretriever
 
-    name: opnfv-lint
-
-    project: opnfv-lint
+    project: '{name}'
 
     jobs:
-        - 'opnfv-lint-verify-{stream}'
+        - 'openretriever-verify-{stream}'
 
     stream:
         - master:
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
 
-########################
-# job templates
-########################
-
 - job-template:
-    name: 'opnfv-lint-verify-{stream}'
+    name: 'openretriever-verify-{stream}'
 
     disabled: '{obj:disabled}'
 
     parameters:
         - project-parameter:
-            project: $GERRIT_PROJECT
-        - gerrit-parameter:
+            project: '{project}'
             branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 - comment-added-contains-event:
                     comment-contains-value: 'reverify'
             projects:
-              - project-compare-type: 'REG_EXP'
-                project-pattern: 'functest|sdnvpn|qtip|daisy'
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
-                file-paths:
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
                   - compare-type: ANT
-                    pattern: '**/*.py'
+                    pattern: 'docs/**|.gitignore'
 
     builders:
-        - lint-python-code
-        - report-lint-result-to-gerrit
+        - shell: |
+            echo "Nothing to verify!"
diff --git a/jjb/opera/opera-daily-jobs.yml b/jjb/opera/opera-daily-jobs.yml
new file mode 100644 (file)
index 0000000..5d2cc03
--- /dev/null
@@ -0,0 +1,147 @@
+- project:
+    name: 'opera-daily-jobs'
+
+    project: 'opera'
+
+#####################################
+# branch definitions
+#####################################
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+
+#####################################
+# patch verification phases
+#####################################
+    phase:
+        - 'basic'
+        - 'deploy'
+
+#####################################
+# jobs
+#####################################
+    jobs:
+        - 'opera-daily-{stream}'
+        - 'opera-daily-{phase}-{stream}'
+#####################################
+# job templates
+#####################################
+- job-template:
+    name: 'opera-daily-{stream}'
+
+    project-type: multijob
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 1
+            max-per-node: 1
+            option: 'project'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - ssh-agent-wrapper
+
+        - timeout:
+            timeout: 240
+            fail: true
+
+    triggers:
+         - timed: '@midnight'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'huawei-virtual7-defaults'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: basic
+            condition: SUCCESSFUL
+            projects:
+                - name: 'opera-daily-basic-{stream}'
+                  current-parameters: true
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: deploy
+            condition: SUCCESSFUL
+            projects:
+                - name: 'compass-deploy-virtual-daily-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    DEPLOY_SCENARIO=os-nosdn-openo-ha
+                    COMPASS_OS_VERSION=xenial
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+#        - multijob:
+#            name: functest
+#            condition: SUCCESSFUL
+#            projects:
+#                - name: 'functest-compass-baremetal-suite-{stream}'
+#                  current-parameters: false
+#                  predefined-parameters:
+#                    FUNCTEST_SUITE_NAME=opera
+#                  node-parameters: true
+#                  kill-phase-on: NEVER
+#                  abort-all-job: true
+
+- job-template:
+    name: 'opera-daily-{phase}-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-per-node: 1
+            option: 'project'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 120
+            fail: true
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - '{project}-daily-{phase}-macro'
+
+#####################################
+# builder macros
+#####################################
+- builder:
+    name: 'opera-daily-basic-macro'
+    builders:
+        - shell: |
+            #!/bin/bash
+            echo "Hello world!"
+
+- builder:
+    name: 'opera-daily-deploy-macro'
+    builders:
+        - shell: |
+            #!/bin/bash
+            echo "Hello world!"
+
diff --git a/jjb/opera/opera-project-jobs.yml b/jjb/opera/opera-project-jobs.yml
new file mode 100644 (file)
index 0000000..38efbc1
--- /dev/null
@@ -0,0 +1,57 @@
+- project:
+
+    name: opera-project
+
+    project: 'opera'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+
+    jobs:
+        - 'opera-build-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: 'opera-build-{stream}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 1
+            max-per-node: 1
+            option: 'project'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+
+    scm:
+        - git-scm
+
+    triggers:
+        - timed: 'H 23 * * *'
+
+    builders:
+        - 'opera-build-macro'
+
+#####################################
+# builder macros
+#####################################
+- builder:
+    name: 'opera-build-macro'
+    builders:
+        - shell: |
+            #!/bin/bash
+
+            echo "Hello world!"
+
+
diff --git a/jjb/opera/opera-verify-jobs.yml b/jjb/opera/opera-verify-jobs.yml
new file mode 100644 (file)
index 0000000..4da41d8
--- /dev/null
@@ -0,0 +1,157 @@
+- project:
+    name: 'opera-verify-jobs'
+
+    project: 'opera'
+
+#####################################
+# branch definitions
+#####################################
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+
+#####################################
+# patch verification phases
+#####################################
+    phase:
+        - 'basic'
+        - 'deploy'
+
+#####################################
+# jobs
+#####################################
+    jobs:
+        - 'opera-verify-{stream}'
+        - 'opera-verify-{phase}-{stream}'
+#####################################
+# job templates
+#####################################
+- job-template:
+    name: 'opera-verify-{stream}'
+
+    project-type: multijob
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-total: 1
+            max-per-node: 1
+            option: 'project'
+
+    scm:
+        - git-scm-gerrit
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 120
+            fail: true
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: '**/*'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**'
+            readable-message: true
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'huawei-pod7-defaults'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: basic
+            condition: SUCCESSFUL
+            projects:
+                - name: 'opera-verify-basic-{stream}'
+                  current-parameters: true
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: deploy
+            condition: SUCCESSFUL
+            projects:
+                - name: 'opera-verify-deploy-{stream}'
+                  current-parameters: true
+                  node-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+
+- job-template:
+    name: 'opera-verify-{phase}-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    properties:
+        - logrotate-default
+        - throttle:
+            enabled: true
+            max-per-node: 1
+            option: 'project'
+
+    scm:
+        - git-scm-gerrit
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 120
+            fail: true
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - '{project}-verify-{phase}-macro'
+
+#####################################
+# builder macros
+#####################################
+- builder:
+    name: 'opera-verify-basic-macro'
+    builders:
+        - shell: |
+            #!/bin/bash
+            echo "Hello world!"
+
+- builder:
+    name: 'opera-verify-deploy-macro'
+    builders:
+        - shell: |
+            #!/bin/bash
+            echo "Hello world!"
+
diff --git a/jjb/opnfv/opnfv-docker.sh b/jjb/opnfv/opnfv-docker.sh
deleted file mode 100644 (file)
index e637f7b..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/bin/bash
-# SPDX-license-identifier: Apache-2.0
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-set -o errexit
-set -o nounset
-set -o pipefail
-
-
-echo "Starting opnfv-docker for $DOCKER_REPO_NAME ..."
-echo "--------------------------------------------------------"
-echo
-
-
-if [[ -n $(ps -ef|grep 'docker build'|grep -v grep) ]]; then
-    echo "There is already another build process in progress:"
-    echo $(ps -ef|grep 'docker build'|grep -v grep)
-    # Abort this job since it will colide and might mess up the current one.
-    echo "Aborting..."
-    exit 1
-fi
-
-# Remove previous running containers if exist
-if [[ -n "$(docker ps -a | grep $DOCKER_REPO_NAME)" ]]; then
-    echo "Removing existing $DOCKER_REPO_NAME containers..."
-    docker ps -a | grep $DOCKER_REPO_NAME | awk '{print $1}' | xargs docker rm -f
-    t=60
-    # Wait max 60 sec for containers to be removed
-    while [[ $t -gt 0 ]] && [[ -n "$(docker ps| grep $DOCKER_REPO_NAME)" ]]; do
-        sleep 1
-        let t=t-1
-    done
-fi
-
-
-# Remove existing images if exist
-if [[ -n "$(docker images | grep $DOCKER_REPO_NAME)" ]]; then
-    echo "Docker images to remove:"
-    docker images | head -1 && docker images | grep $DOCKER_REPO_NAME
-    image_tags=($(docker images | grep $DOCKER_REPO_NAME | awk '{print $2}'))
-    for tag in "${image_tags[@]}"; do
-        if [[ -n "$(docker images|grep $DOCKER_REPO_NAME|grep $tag)" ]]; then
-            echo "Removing docker image $DOCKER_REPO_NAME:$tag..."
-            docker rmi -f $DOCKER_REPO_NAME:$tag
-        fi
-    done
-fi
-
-# If we just want to update the latest_stable image
-if [[ "$UPDATE_LATEST_STABLE" == "true" ]]; then
-    echo "Pulling $DOCKER_REPO_NAME:$STABLE_TAG ..."
-    docker pull $DOCKER_REPO_NAME:$STABLE_TAG
-    if [[ $? -ne 0 ]]; then
-        echo "ERROR: The image $DOCKER_REPO_NAME with tag $STABLE_TAG does not exist."
-        exit 1
-    fi
-    docker tag $DOCKER_REPO_NAME:$STABLE_TAG $DOCKER_REPO_NAME:latest_stable
-    echo "Pushing $DOCKER_REPO_NAME:latest_stable ..."
-    docker push $DOCKER_REPO_NAME:latest_stable
-    exit 0
-fi
-
-
-# cd to directory where Dockerfile is located
-cd $WORKSPACE/docker
-if [ ! -f ./Dockerfile ]; then
-    echo "ERROR: Dockerfile not found."
-    exit 1
-fi
-
-# Get tag version
-branch="${GIT_BRANCH##origin/}"
-echo "Current branch: $branch"
-
-if [[ "$branch" == "master" ]]; then
-    DOCKER_TAG="master"
-    DOCKER_BRANCH_TAG="latest"
-else
-    git clone https://gerrit.opnfv.org/gerrit/releng $WORKSPACE/releng
-
-    DOCKER_TAG=$($WORKSPACE/releng/utils/calculate_version.sh -t docker \
-        -n $DOCKER_REPO_NAME)
-    DOCKER_BRANCH_TAG="stable"
-
-    ret_val=$?
-    if [[ $ret_val -ne 0 ]]; then
-        echo "Error retrieving the version tag."
-        exit 1
-    fi
-fi
-echo "Tag version to be build and pushed: $DOCKER_TAG"
-
-
-# Start the build
-echo "Building docker image: $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG"
-
-if [[ $DOCKER_REPO_NAME == *"functest"* ]]; then
-    docker build --no-cache -t $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG --build-arg BRANCH=$branch .
-else
-    docker build --no-cache -t $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG .
-fi
-
-echo "Creating tag '$DOCKER_TAG'..."
-docker tag $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG $DOCKER_REPO_NAME:$DOCKER_TAG
-
-# list the images
-echo "Available images are:"
-docker images
-
-# Push image to Dockerhub
-if [[ "$PUSH_IMAGE" == "true" ]]; then
-    echo "Pushing $DOCKER_REPO_NAME:$DOCKER_TAG to the docker registry..."
-    echo "--------------------------------------------------------"
-    echo
-    # Push to the Dockerhub repository
-    echo "Pushing $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG ..."
-    docker push $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG
-
-    echo "Pushing $DOCKER_REPO_NAME:$DOCKER_TAG ..."
-    docker push $DOCKER_REPO_NAME:$DOCKER_TAG
-fi
diff --git a/jjb/opnfv/opnfv-docker.yml b/jjb/opnfv/opnfv-docker.yml
deleted file mode 100644 (file)
index f313b3b..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-##############################################
-# job configuration for docker build and push
-##############################################
-
-- project:
-
-    name: opnfv-docker
-
-    project:
-        - 'bottlenecks'
-        - 'cperf'
-        - 'functest'
-        - 'storperf'
-        - 'qtip'
-
-    jobs:
-        - '{project}-docker-build-push-{stream}'
-        - 'yardstick-docker-build-push-{stream}'
-        #dovetail not sync with release, an independent job
-        #only master by now, will adjust accordingly in future
-        - 'dovetail-docker-build-push-{dovetailstream}'
-
-    stream:
-        - master:
-            branch: '{stream}'
-            disabled: false
-        - colorado:
-            branch: 'stable/{stream}'
-            disabled: false
-    dovetailstream:
-        - master:
-            branch: '{dovetailstream}'
-            disabled: false
-
-########################
-# job templates
-########################
-- job-template:
-    name: '{project}-docker-build-push-{stream}'
-
-    disabled: '{obj:disabled}'
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-        - 'opnfv-build-ubuntu-defaults'
-        - string:
-            name: PUSH_IMAGE
-            default: "true"
-            description: "To enable/disable pushing the image to Dockerhub."
-        - string:
-            name: BASE_VERSION
-            default: "colorado.0"
-            description: "Base version to be used."
-        - string:
-            name: DOCKER_REPO_NAME
-            default: "opnfv/{project}"
-            description: "Dockerhub repo to be pushed to."
-        - string:
-            name: UPDATE_LATEST_STABLE
-            default: "false"
-            description: "This will update the latest_stable image only."
-        - string:
-            name: STABLE_TAG
-            description: "If above option is true, this is the tag to be pulled."
-
-    scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
-
-    builders:
-        - shell:
-            !include-raw-escape: ./opnfv-docker.sh
-
-    triggers:
-        - pollscm:
-            cron: "*/30 * * * *"
-
-- job-template:
-    name: 'yardstick-docker-build-push-{stream}'
-
-    disabled: '{obj:disabled}'
-
-    parameters:
-        - project-parameter:
-            project: 'yardstick'
-        - 'opnfv-build-ubuntu-defaults'
-        - string:
-            name: PUSH_IMAGE
-            default: "true"
-            description: "To enable/disable pushing the image to Dockerhub."
-        - string:
-            name: BASE_VERSION
-            default: "colorado.0"
-            description: "Base version to be used."
-        - string:
-            name: DOCKER_REPO_NAME
-            default: "opnfv/yardstick"
-            description: "Dockerhub repo to be pushed to."
-        - string:
-            name: UPDATE_LATEST_STABLE
-            default: "false"
-            description: "This will update the latest_stable image only."
-        - string:
-            name: STABLE_TAG
-            description: "If above option is true, this is the tag to be pulled."
-
-    scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
-
-    builders:
-        - shell:
-            !include-raw-escape: ./opnfv-docker.sh
-
-    triggers:
-        - pollscm:
-            cron: "*/30 * * * *"
-
-- job-template:
-    name: 'dovetail-docker-build-push-{dovetailstream}'
-
-    disabled: '{obj:disabled}'
-
-    parameters:
-        - project-parameter:
-            project: 'dovetail'
-        - 'opnfv-build-ubuntu-defaults'
-        - string:
-            name: PUSH_IMAGE
-            default: "true"
-            description: "To enable/disable pushing the image to Dockerhub."
-        #BASE_VERSION parameter is used for version control
-        #by now, only master branch is used, this parameter takes no effect
-        #once branch control settled, should be adjusted togather with
-        #opnfv-docker.sh and caculate_version.sh
-        - string:
-            name: BASE_VERSION
-            default: "1.0"
-            description: "Base version to be used."
-        - string:
-            name: DOCKER_REPO_NAME
-            default: "opnfv/dovetail"
-            description: "Dockerhub repo to be pushed to."
-        - string:
-            name: UPDATE_LATEST_STABLE
-            default: "false"
-            description: "This will update the latest_stable image only."
-        - string:
-            name: STABLE_TAG
-            description: "If above option is true, this is the tag to be pulled."
-
-    scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
-
-    builders:
-        - shell:
-            !include-raw-escape: ./opnfv-docker.sh
-
-    triggers:
-        - pollscm:
-            cron: "*/30 * * * *"
diff --git a/jjb/opnfv/test-sign.yml b/jjb/opnfv/test-sign.yml
deleted file mode 100644 (file)
index b27d757..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-- project:
-    name: test-sign
-
-    project: 'releng'
-
-    jobs:
-        - 'test-sign-daily-{stream}'
-
-    stream:
-        - master:
-            branch: '{stream}'
-            gs-pathname: ''
-
-
-- job-template:
-    name: 'test-sign-daily-{stream}'
-
-    # Job template for daily builders
-    #
-    # Required Variables:
-    #     stream:    branch with - in place of / (eg. stable)
-    #     branch:    branch (eg. stable)
-    node: master
-
-    disabled: false
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-
-    scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
-
-    triggers:
-        - timed: 'H H * * *'
-
-    builders:
-        - shell: |
-            $WORKSPACE/utils/test-sign-artifact.sh
diff --git a/jjb/opnfvdocs/docs-rtd.yaml b/jjb/opnfvdocs/docs-rtd.yaml
new file mode 100644 (file)
index 0000000..864626b
--- /dev/null
@@ -0,0 +1,91 @@
+- project:
+    name: docs-rtd
+    jobs:
+        - 'docs-merge-rtd-{stream}'
+        - 'docs-verify-rtd-{stream}'
+
+    stream:
+        - master:
+            branch: 'master'
+        - danube:
+            branch: 'stable/{stream}'
+
+    project: 'opnfvdocs'
+    rtdproject: 'opnfv'
+    # TODO: Archive Artifacts
+
+- job-template:
+    name: 'docs-merge-rtd-{stream}'
+
+    project-type: freestyle
+
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'lf-build1'
+            description: 'Slave label on Jenkins'
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+
+    triggers:
+        - gerrit-trigger-change-merged:
+            project: '**'
+            branch: '{branch}'
+            files: 'docs/**/*.*'
+
+    builders:
+        - shell: |
+            if [ $GERRIT_BRANCH == "master" ]; then
+              RTD_BUILD_VERSION=latest
+            else
+              RTD_BUILD_VERSION=${{GERRIT_BRANCH/\//-}}
+            fi
+            curl -X POST --data "version_slug=$RTD_BUILD_VERSION" https://readthedocs.org/build/opnfvdocsdemo
+
+
+- job-template:
+    name: 'docs-verify-rtd-{stream}'
+
+    project-type: freestyle
+
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'lf-build2'
+            description: 'Slave label on Jenkins'
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/opnfvdocs
+            description: 'Git URL to use on this Jenkins Slave'
+    scm:
+        - git-scm-with-submodules:
+            branch: '{branch}'
+
+    triggers:
+        - gerrit-trigger-patchset-created:
+            server: 'gerrit.opnfv.org'
+            project: '**'
+            branch: '{branch}'
+            files: 'docs/**/*.*'
+        - timed: 'H H * * *'
+
+    builders:
+        - shell: |
+            if [ "$GERRIT_PROJECT" != "opnfvdocs" ]; then
+                cd docs/submodules/$GERRIT_PROJECT
+                git fetch origin $GERRIT_REFSPEC && git checkout FETCH_HEAD
+            else
+                git fetch origin $GERRIT_REFSPEC && git checkout FETCH_HEAD
+            fi
+        - shell: |
+            sudo pip install virtualenv 
+            virtualenv $WORKSPACE/venv
+            . $WORKSPACE/venv/bin/activate
+            pip install --upgrade pip
+            pip freeze
+            pip install tox
+            tox -edocs
index 2bf87c2..fc825ff 100644 (file)
@@ -8,8 +8,8 @@
     project: '{name}'
 
     jobs:
-        - 'opnfvdocs-verify-{stream}'
-        - 'opnfvdocs-merge-{stream}'
+        - 'opnfvdocs-verify-shellcheck-{stream}'
+        - 'opnfvdocs-merge-shellcheck-{stream}'
         - 'opnfvdocs-daily-{stream}'
 
     stream:
@@ -17,7 +17,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
 ########################
 
 - job-template:
-    name: 'opnfvdocs-verify-{stream}'
+    name: 'opnfvdocs-verify-shellcheck-{stream}'
 
     disabled: '{obj:disabled}'
 
     parameters:
         - project-parameter:
             project: $GERRIT_PROJECT
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_CLONE_BASE
             description: "Used for overriding the GIT URL coming from parameters macro."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+            skip-vote:
+                successful: true
+                failed: true
+                unstable: true
+                notbuilt: true
 
     builders:
         - check-bash-syntax
 
 - job-template:
-    name: 'opnfvdocs-merge-{stream}'
+    name: 'opnfvdocs-merge-shellcheck-{stream}'
 
     disabled: '{obj:disabled}'
 
     parameters:
         - project-parameter:
             project: $GERRIT_PROJECT
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_CLONE_BASE
             description: "Directory where the build artifact will be located upon the completion of the build."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - string:
             name: GS_URL
             default: '$GS_BASE{gs-pathname}'
             name: GIT_CLONE_BASE
             default: ssh://gerrit.opnfv.org:29418
             description: "Used for overriding the GIT URL coming from parameters macro."
-        - string:
-            name: GERRIT_BRANCH
-            default: '{branch}'
-            description: 'Specify the branch in this way in order to be able to use build-opnfv-composite-docs builder.'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - timed: '0 H/6 * * *'
     builders:
         - build-html-and-pdf-docs-output
 #        - upload-generated-docs-to-opnfv-artifacts
-
index 7f02361..1ea05c1 100644 (file)
@@ -5,6 +5,7 @@ bottlenecks
 compass4nfv
 copper
 conductor
+daisy
 doctor
 domino
 dovetail
@@ -23,6 +24,7 @@ movie
 multisite
 octopus
 onosfw
+openretriever
 ovno
 ovsnfv
 parser
index c6f3e4a..0e8c713 100644 (file)
@@ -13,7 +13,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
@@ -26,7 +26,6 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-centos-defaults'
         - string:
             description: "Directory where the build artifact will be located upon the completion of the build."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -75,7 +72,6 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-centos-defaults'
         - string:
             description: "Directory where the build artifact will be located upon the completion of the build."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     wrappers:
         - timeout:
@@ -96,6 +89,7 @@
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - 'opnfv-build-centos-defaults'
         - string:
             name: GS_URL
             description: "Directory where the build artifact will be located upon the completion of the build."
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     wrappers:
         - timeout:
index 7f73a13..35e97c3 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
-                    pattern: 'docs/**|.gitignore'
+                    pattern: 'docs/**'
+                  - compare-type: ANT
+                    pattern: 'governance/**'
+                  - compare-type: ANT
+                    pattern: '*.txt|.gitignore|.gitreview|INFO|LICENSE'
 
     builders:
         - shell: |
index f7ea622..12ae5ca 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index a95cd98..a153a9b 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index 1a4d628..eeace5f 100644 (file)
@@ -15,7 +15,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -56,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
similarity index 60%
rename from jjb/qtip/qtip-cleanup.sh
rename to jjb/qtip/helpers/cleanup-deploy.sh
index 95babb3..9cb19a5 100644 (file)
@@ -7,20 +7,15 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 # Remove previous running containers if exist
-if [[ ! -z $(docker ps -a | grep opnfv/qtip) ]]; then
+if [[ ! -z $(docker ps -a | grep "opnfv/qtip:$DOCKER_TAG") ]]; then
     echo "Removing existing opnfv/qtip containers..."
     # workaround: sometimes it throws an error when stopping qtip container.
     # To make sure ci job unblocked, remove qtip container by force without stopping it.
-    docker rm -f $(docker ps -a | grep opnfv/qtip | awk '{print $1}')
+    docker rm -f $(docker ps -a | grep "opnfv/qtip:$DOCKER_TAG" | awk '{print $1}')
 fi
 
 # Remove existing images if exist
-if [[ ! -z $(docker images | grep opnfv/qtip) ]]; then
-    echo "Docker images to remove:"
-    docker images | head -1 && docker images | grep opnfv/qtip
-    image_tags=($(docker images | grep opnfv/qtip | awk '{print $2}'))
-    for tag in "${image_tags[@]}"; do
-        echo "Removing docker image opnfv/qtip:$tag..."
-        docker rmi opnfv/qtip:$tag
-    done
+if [[ $(docker images opnfv/qtip:${DOCKER_TAG} | wc -l) -gt 1 ]]; then
+    echo "Removing docker image opnfv/qtip:$DOCKER_TAG..."
+    docker rmi opnfv/qtip:$DOCKER_TAG
 fi
diff --git a/jjb/qtip/helpers/validate-deploy.sh b/jjb/qtip/helpers/validate-deploy.sh
new file mode 100644 (file)
index 0000000..9f3dbe4
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2016 ZTE and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -e
+
+envs="INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP}
+-e NODE_NAME=${NODE_NAME} -e CI_DEBUG=${CI_DEBUG}"
+ramfs=/tmp/qtip/ramfs
+cfg_dir=$(dirname $ramfs)
+dir_imgstore="${HOME}/imgstore"
+ramfs_volume="$ramfs:/mnt/ramfs"
+
+echo "--------------------------------------------------------"
+echo "POD: $NODE_NAME"
+echo "INSTALLER: $INSTALLER_TYPE"
+echo "Scenario: $DEPLOY_SCENARIO"
+echo "--------------------------------------------------------"
+
+echo "Qtip: Pulling docker image: opnfv/qtip:${DOCKER_TAG}"
+docker pull opnfv/qtip:$DOCKER_TAG
+
+# use ramfs to fix docker socket connection issue with overlay mode in centos
+if [ ! -d $ramfs ]; then
+    mkdir -p $ramfs
+fi
+
+if [ ! -z "$(df $ramfs | tail -n -1 | grep $ramfs)" ]; then
+    sudo mount -t tmpfs -o size=32M tmpfs $ramfs
+fi
+
+# enable contro path in docker
+cat <<EOF > ${cfg_dir}/ansible.cfg
+[defaults]
+callback_whitelist = profile_tasks
+[ssh_connection]
+control_path=/mnt/ramfs/ansible-ssh-%%h-%%p-%%r
+EOF
+
+cmd=" docker run -id -e $envs -v ${ramfs_volume} opnfv/qtip:${DOCKER_TAG} /bin/bash"
+echo "Qtip: Running docker command: ${cmd}"
+${cmd}
+
+container_id=$(docker ps | grep "opnfv/qtip:${DOCKER_TAG}" | awk '{print $1}' | head -1)
+if [ $(docker ps | grep 'opnfv/qtip' | wc -l) == 0 ]; then
+    echo "The container opnfv/qtip with ID=${container_id} has not been properly started. Exiting..."
+    exit 1
+else
+    echo "The container ID is: ${container_id}"
+    QTIP_REPO=/home/opnfv/repos/qtip
+    docker cp ${cfg_dir}/ansible.cfg ${container_id}:/home/opnfv/.ansible.cfg
+# TODO(zhihui_wu): use qtip cli to execute benchmark test in the future
+    docker exec -t ${container_id} bash -c "cd ${QTIP_REPO}/qtip/runner/ &&
+    python runner.py -d /home/opnfv/qtip/results/ -b all"
+
+fi
+
+echo "Qtip done!"
diff --git a/jjb/qtip/helpers/validate-setup.sh b/jjb/qtip/helpers/validate-setup.sh
new file mode 100644 (file)
index 0000000..8d84e12
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+##############################################################################
+# Copyright (c) 2017 ZTE and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+set -e
+
+# setup virtualenv
+sudo pip install -u virtualenv virtualenvwrapper
+export WORKON_HOME=$HOME/.virtualenvs
+source /usr/local/bin/virtualenvwrapper.sh
+mkvirtualenv qtip
+workon qtip
+
+# setup qtip
+sudo pip install $HOME/repos/qtip
+
+# testing
+qtip --version
+qtip --help
diff --git a/jjb/qtip/qtip-ci-jobs.yml b/jjb/qtip/qtip-ci-jobs.yml
deleted file mode 100644 (file)
index cca8cee..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-####################################
-# job configuration for qtip
-####################################
-- project:
-    name: qtip-ci-jobs
-
-    project: 'qtip'
-
-#--------------------------------
-# BRANCH ANCHORS
-#--------------------------------
-    master: &master
-        stream: master
-        branch: '{stream}'
-        gs-pathname: ''
-        docker-tag: 'latest'
-#--------------------------------
-# POD, INSTALLER, AND BRANCH MAPPING
-#--------------------------------
-#        master
-#--------------------------------
-    pod:
-        - dell-pod1:
-            installer: compass
-            auto-trigger-name: 'daily-trigger-disabled'
-            <<: *master
-        - orange-pod2:
-            installer: joid
-            auto-trigger-name: 'daily-trigger-disabled'
-            <<: *master
-        - juniper-pod1:
-            installer: joid
-            <<: *master
-            auto-trigger-name: 'daily-trigger-disabled'
-        - zte-pod1:
-            installer: fuel
-            auto-trigger-name: 'daily-trigger-disabled'
-            <<: *master
-        - zte-pod2:
-            installer: fuel
-            auto-trigger-name: 'qtip-daily-zte-pod2-trigger'
-            <<: *master
-        - zte-pod3:
-            installer: fuel
-            auto-trigger-name: 'qtip-daily-zte-pod3-trigger'
-            <<: *master
-
-#--------------------------------
-    jobs:
-        - 'qtip-{installer}-{pod}-daily-{stream}'
-
-################################
-# job templates
-################################
-- job-template:
-    name: 'qtip-{installer}-{pod}-daily-{stream}'
-
-    disabled: false
-
-    parameters:
-        - project-parameter:
-            project: '{project}'
-        - '{installer}-defaults'
-        - '{pod}-defaults'
-        - string:
-            name: DEPLOY_SCENARIO
-            default: 'os-nosdn-nofeature-ha'
-        - string:
-            name: DOCKER_TAG
-            default: '{docker-tag}'
-            description: 'Tag to pull docker image'
-
-    scm:
-        - git-scm:
-              credentials-id: '{ssh-credentials}'
-              refspec: ''
-              branch: '{branch}'
-
-    triggers:
-        - '{auto-trigger-name}'
-
-    builders:
-        - 'qtip-cleanup'
-        - 'qtip-daily-ci'
-
-    publishers:
-        - email:
-            recipients: nauman.ahad@xflowresearch.com, mofassir.arif@xflowresearch.com, vikram@nvirters.com, zhang.yujunz@zte.com.cn
-
-###########################
-#biuilder macros
-###########################
-- builder:
-    name: qtip-daily-ci
-    builders:
-        - shell:
-            !include-raw: ./qtip-daily-ci.sh
-
-- builder:
-    name: qtip-cleanup
-    builders:
-        - shell:
-            !include-raw: ./qtip-cleanup.sh
-
-#################
-#trigger macros
-#################
-
-#- trigger:
-#    name: 'qtip-daily-dell-pod1-trigger'
-#    triggers:
-#        - timed: '0 3 * * *'
-
-#- trigger:
-#    name: 'qtip-daily-juniper-pod1-trigger'
-#    triggers:
-#        - timed : '0 0 * * *'
-
-#- trigger:
-#   name: 'qtip-dailty-orange-pod2-trigger'
-#   triggers:
-#       - timed : ' 0 0 * * *'
-
-- trigger:
-    name: 'qtip-daily-zte-pod2-trigger'
-    triggers:
-        - timed: '0 7 * * *'
-
-- trigger:
-    name: 'qtip-daily-zte-pod3-trigger'
-    triggers:
-        - timed: '0 1 * * *'
diff --git a/jjb/qtip/qtip-daily-ci.sh b/jjb/qtip/qtip-daily-ci.sh
deleted file mode 100644 (file)
index 4fdc043..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-##############################################################################
-# Copyright (c) 2016 ZTE and others.
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-set -e
-
-envs="INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP} -e NODE_NAME=${NODE_NAME}"
-suite="TEST_CASE=all"
-dir_imgstore="${HOME}/imgstore"
-img_volume="${dir_imgstore}:/home/opnfv/imgstore"
-
-echo "Qtip: Pulling docker image: opnfv/qtip:${DOCKER_TAG}"
-docker pull opnfv/qtip:$DOCKER_TAG
-
-cmd=" docker run -id -e $envs -e $suite -v ${img_volume} opnfv/qtip:${DOCKER_TAG} /bin/bash"
-echo "Qtip: Running docker command: ${cmd}"
-${cmd}
-
-container_id=$(docker ps | grep "opnfv/qtip:${DOCKER_TAG}" | awk '{print $1}' | head -1)
-if [ $(docker ps | grep 'opnfv/qtip' | wc -l) == 0 ]; then
-    echo "The container opnfv/qtip with ID=${container_id} has not been properly started. Exiting..."
-    exit 1
-else
-    echo "The container ID is: ${container_id}"
-    QTIP_REPO=/home/opnfv/repos/qtip
-
-    echo "Run Qtip test"
-    docker exec -t ${container_id} $QTIP_REPO/docker/run_qtip.sh
-
-    echo "Pushing available results to DB"
-    docker exec -t ${container_id} $QTIP_REPO/docker/push_db.sh
-fi
-
-echo "Qtip done!"
diff --git a/jjb/qtip/qtip-validate-jobs.yml b/jjb/qtip/qtip-validate-jobs.yml
new file mode 100644 (file)
index 0000000..8dd97de
--- /dev/null
@@ -0,0 +1,144 @@
+#######################
+# validate after MERGE
+#######################
+- project:
+    name: qtip
+    project: qtip
+
+#--------------------------------
+# BRANCH ANCHORS
+#--------------------------------
+    master: &master
+        stream: master
+        branch: '{stream}'
+        gs-pathname: ''
+        docker-tag: latest
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        gs-pathname: '/{stream}'
+        docker-tag: 'stable'
+
+#--------------------------------
+# JOB VARIABLES
+#--------------------------------
+    pod:
+        - zte-pod1:
+            installer: fuel
+            scenario: os-odl_l2-nofeature-ha
+            <<: *master
+        - zte-pod3:
+            installer: fuel
+            scenario: os-nosdn-kvm-ha
+            <<: *master
+        - zte-pod1:
+            installer: fuel
+            scenario: os-odl_l2-nofeature-ha
+            <<: *danube
+        - zte-pod3:
+            installer: fuel
+            scenario: os-nosdn-nofeature-ha
+            <<: *danube
+        - zte-pod3:
+            installer: fuel
+            scenario: os-nosdn-kvm-ha
+            <<: *danube
+
+#--------------------------------
+# JOB LIST
+#--------------------------------
+    jobs:
+        - 'qtip-{scenario}-{pod}-daily-{stream}'
+
+################################
+# job templates
+################################
+- job-template:
+    name: 'qtip-{scenario}-{pod}-daily-{stream}'
+    disabled: false
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - '{installer}-defaults'
+        - '{pod}-defaults'
+        - string:
+            name: DEPLOY_SCENARIO
+            default: '{scenario}'
+        - string:
+            name: DOCKER_TAG
+            default: '{docker-tag}'
+            description: 'Tag to pull docker image'
+        - string:
+            name: CI_DEBUG
+            default: 'false'
+            description: "Show debug output information"
+    scm:
+        - git-scm
+    triggers:
+        - 'qtip-{scenario}-{pod}-daily-{stream}-trigger'
+    builders:
+        - description-setter:
+            description: "POD: $NODE_NAME"
+        - qtip-validate-deploy
+    publishers:
+        - qtip-common-publishers
+
+################
+# MARCOS
+################
+
+#---------
+# builder
+#---------
+- builder:
+    name: qtip-validate-deploy
+    builders:
+        - shell:
+            !include-raw: ./helpers/cleanup-deploy.sh
+        - shell:
+            !include-raw: ./helpers/validate-deploy.sh
+
+
+#-----------
+# parameter
+#-----------
+
+#-----------
+# publisher
+#-----------
+
+- publisher:
+    name: qtip-common-publishers
+    publishers:
+        - email:
+            recipients: wu.zhihui1@zte.com.cn, zhang.yujunz@zte.com.cn
+
+#---------
+# trigger
+#---------
+
+- trigger:
+    name: 'qtip-os-odl_l2-nofeature-ha-zte-pod1-daily-master-trigger'
+    triggers:
+        - timed: '0 15 * * *'
+
+- trigger:
+    name: 'qtip-os-nosdn-kvm-ha-zte-pod3-daily-master-trigger'
+    triggers:
+        - timed: '0 15 * * *'
+
+- trigger:
+    name: 'qtip-os-odl_l2-nofeature-ha-zte-pod1-daily-danube-trigger'
+    triggers:
+        - timed: '0 7 * * *'
+
+- trigger:
+    name: 'qtip-os-nosdn-kvm-ha-zte-pod3-daily-danube-trigger'
+    triggers:
+        - timed: '0 7 * * *'
+
+- trigger:
+    name: 'qtip-os-nosdn-nofeature-ha-zte-pod3-daily-danube-trigger'
+    triggers:
+        - timed: '30 0 * * *'
diff --git a/jjb/qtip/qtip-verify-jobs.yml b/jjb/qtip/qtip-verify-jobs.yml
new file mode 100644 (file)
index 0000000..dd444c7
--- /dev/null
@@ -0,0 +1,78 @@
+######################
+# verify before MERGE
+######################
+
+- project:
+    name: qtip-verify-jobs
+    project: qtip
+    jobs:
+        - 'qtip-verify-{stream}'
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+
+################################
+## job templates
+#################################
+- job-template:
+    name: 'qtip-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**|.gitignore'
+
+    builders:
+        - qtip-unit-tests-and-docs-build
+    publishers:
+        - publish-coverage
+
+################################
+## job builders
+#################################
+- builder:
+    name: qtip-unit-tests-and-docs-build
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o pipefail
+            set -o xtrace
+
+            tox
diff --git a/jjb/releng-defaults.yaml b/jjb/releng-defaults.yaml
deleted file mode 100644 (file)
index bf33306..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-# jjb defaults
-
-- defaults:
-    name: global
-
-    logrotate:
-        daysToKeep: 60
-        numToKeep: 200
-        artifactDaysToKeep: 30
-        artifactNumToKeep: 100
-
-    ssh-credentials: 'd42411ac011ad6f3dd2e1fa34eaa5d87f910eb2e'
-    wrappers:
-        - ssh-agent-credentials:
-            users:
-                - '{ssh-credentials}'
-
-    project-type: freestyle
-
-    node: master
-
similarity index 74%
rename from jjb/opnfv/artifact-cleanup.yml
rename to jjb/releng/artifact-cleanup.yml
index b0f8191..2d02056 100644 (file)
@@ -1,10 +1,10 @@
 - project:
-    name: artifact-cleanup
+    name: releng-artifact-cleanup
 
     project: 'releng'
 
     jobs:
-        - 'artifact-cleanup-daily-{stream}'
+        - 'releng-artifact-cleanup-daily-{stream}'
 
     stream:
         - master:
@@ -13,7 +13,7 @@
 
 
 - job-template:
-    name: 'artifact-cleanup-daily-{stream}'
+    name: 'releng-artifact-cleanup-daily-{stream}'
 
     # Job template for daily builders
     #
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - timed: 'H H * * *'
diff --git a/jjb/releng/opnfv-docker-arm.yml b/jjb/releng/opnfv-docker-arm.yml
new file mode 100644 (file)
index 0000000..ba540ed
--- /dev/null
@@ -0,0 +1,77 @@
+##############################################
+# job configuration for docker build and push
+##############################################
+
+- project:
+
+    name: opnfv-docker-arm
+
+    master: &master
+        stream: master
+        branch: '{stream}'
+        disabled: false
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        disabled: false
+    functest-arm-receivers: &functest-arm-receivers
+        receivers: >
+            cristina.pauna@enea.com
+            alexandru.avadanii@enea.com
+    other-receivers: &other-receivers
+        receivers: ''
+
+    project:
+        # projects with jobs for master
+        - 'functest':
+            <<: *master
+            <<: *functest-arm-receivers
+        # projects with jobs for stable
+
+    jobs:
+        - '{project}-docker-build-arm-push-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: '{project}-docker-build-arm-push-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters: &parameters
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-arm-defaults'
+        - string:
+            name: PUSH_IMAGE
+            default: "true"
+            description: "To enable/disable pushing the image to Dockerhub."
+        - string:
+            name: DOCKER_REPO_NAME
+            default: "opnfv/{project}_aarch64"
+            description: "Dockerhub repo to be pushed to."
+        - string:
+            name: RELEASE_VERSION
+            default: ""
+            description: "Release version, e.g. 1.0, 2.0, 3.0"
+        - string:
+            name: DOCKERFILE
+            default: "Dockerfile.aarch64"
+            description: "Dockerfile to use for creating the image."
+
+    scm:
+        - git-scm
+
+    builders: &builders
+        - shell:
+            !include-raw-escape: ./opnfv-docker.sh
+
+    triggers:
+        - pollscm:
+            cron: "*/30 * * * *"
+
+    publishers:
+        - email:
+            recipients: '{receivers}'
diff --git a/jjb/releng/opnfv-docker.sh b/jjb/releng/opnfv-docker.sh
new file mode 100644 (file)
index 0000000..5d73a9d
--- /dev/null
@@ -0,0 +1,113 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o nounset
+set -o pipefail
+
+
+
+echo "Starting opnfv-docker for $DOCKER_REPO_NAME ..."
+echo "--------------------------------------------------------"
+echo
+
+count=30 # docker build jobs might take up to ~30 min
+while [[ -n `ps -ef|grep 'docker build'|grep -v grep` ]]; do
+    echo "Build in progress. Waiting..."
+    sleep 60
+    count=$(( $count - 1 ))
+    if [ $count -eq 0 ]; then
+        echo "Timeout. Aborting..."
+        exit 1
+    fi
+done
+
+# Remove previous running containers if exist
+if [[ -n "$(docker ps -a | grep $DOCKER_REPO_NAME)" ]]; then
+    echo "Removing existing $DOCKER_REPO_NAME containers..."
+    docker ps -a | grep $DOCKER_REPO_NAME | awk '{print $1}' | xargs docker rm -f
+    t=60
+    # Wait max 60 sec for containers to be removed
+    while [[ $t -gt 0 ]] && [[ -n "$(docker ps| grep $DOCKER_REPO_NAME)" ]]; do
+        sleep 1
+        let t=t-1
+    done
+fi
+
+
+# Remove existing images if exist
+if [[ -n "$(docker images | grep $DOCKER_REPO_NAME)" ]]; then
+    echo "Docker images to remove:"
+    docker images | head -1 && docker images | grep $DOCKER_REPO_NAME
+    image_ids=($(docker images | grep $DOCKER_REPO_NAME | awk '{print $3}'))
+    for id in "${image_ids[@]}"; do
+        if [[ -n "$(docker images|grep $DOCKER_REPO_NAME|grep $id)" ]]; then
+            echo "Removing docker image $DOCKER_REPO_NAME:$id..."
+            docker rmi -f $id
+        fi
+    done
+fi
+
+cd $WORKSPACE/docker
+HOST_ARCH=$(uname -m)
+if [ ! -f "${DOCKERFILE}" ]; then
+    # If this is expected to be a Dockerfile for other arch than x86
+    # and it does not exist, but there is a patch for the said arch,
+    # then apply the patch and create the Dockerfile.${HOST_ARCH} file
+    if [[ "${DOCKERFILE}" == *"${HOST_ARCH}" && \
+          -f "Dockerfile.${HOST_ARCH}.patch" ]]; then
+        patch -o Dockerfile."${HOST_ARCH}" Dockerfile \
+        Dockerfile."${HOST_ARCH}".patch
+    else
+        echo "ERROR: No Dockerfile or ${HOST_ARCH} patch found."
+        exit 1
+    fi
+fi
+
+# Get tag version
+echo "Current branch: $BRANCH"
+
+if [[ "$BRANCH" == "master" ]]; then
+    DOCKER_TAG="latest"
+else
+    if [[ -n "${RELEASE_VERSION-}" ]]; then
+        release=${BRANCH##*/}
+        DOCKER_TAG=${release}.${RELEASE_VERSION}
+        # e.g. colorado.1.0, colorado.2.0, colorado.3.0
+    else
+        DOCKER_TAG="stable"
+    fi
+fi
+
+# Start the build
+echo "Building docker image: $DOCKER_REPO_NAME:$DOCKER_TAG"
+echo "--------------------------------------------------------"
+echo
+if [[ $DOCKER_REPO_NAME == *"dovetail"* ]]; then
+    cmd="docker build --no-cache -t $DOCKER_REPO_NAME:$DOCKER_TAG -f $DOCKERFILE ."
+else
+    cmd="docker build --no-cache -t $DOCKER_REPO_NAME:$DOCKER_TAG --build-arg BRANCH=$BRANCH
+        -f $DOCKERFILE ."
+fi
+
+echo ${cmd}
+${cmd}
+
+
+# list the images
+echo "Available images are:"
+docker images
+
+# Push image to Dockerhub
+if [[ "$PUSH_IMAGE" == "true" ]]; then
+    echo "Pushing $DOCKER_REPO_NAME:$DOCKER_TAG to the docker registry..."
+    echo "--------------------------------------------------------"
+    echo
+    docker push $DOCKER_REPO_NAME:$DOCKER_TAG
+fi
diff --git a/jjb/releng/opnfv-docker.yml b/jjb/releng/opnfv-docker.yml
new file mode 100644 (file)
index 0000000..3b7ec34
--- /dev/null
@@ -0,0 +1,153 @@
+##############################################
+# job configuration for docker build and push
+##############################################
+
+- project:
+
+    name: opnfv-docker
+
+    master: &master
+        stream: master
+        branch: '{stream}'
+        disabled: false
+    danube: &danube
+        stream: danube
+        branch: 'stable/{stream}'
+        disabled: false
+    functest-receivers: &functest-receivers
+        receivers: >
+            jose.lausuch@ericsson.com morgan.richomme@orange.com
+            cedric.ollivier@orange.com feng.xiaowei@zte.com.cn
+            yaohelan@huawei.com helanyao@gmail.com
+            juha.kosonen@nokia.com
+    other-receivers: &other-receivers
+        receivers: ''
+
+    project:
+        # projects with jobs for master
+        - 'bottlenecks':
+            <<: *master
+            <<: *other-receivers
+        - 'cperf':
+            <<: *master
+            <<: *other-receivers
+        - 'dovetail':
+            <<: *master
+            <<: *other-receivers
+        - 'functest':
+            <<: *master
+            <<: *functest-receivers
+        - 'qtip':
+            <<: *master
+            <<: *other-receivers
+        - 'storperf':
+            <<: *master
+            <<: *other-receivers
+        - 'yardstick':
+            <<: *master
+            <<: *other-receivers
+        # projects with jobs for stable
+        - 'bottlenecks':
+            <<: *danube
+            <<: *other-receivers
+        - 'functest':
+            <<: *danube
+            <<: *functest-receivers
+        - 'qtip':
+            <<: *danube
+            <<: *other-receivers
+        - 'storperf':
+            <<: *danube
+            <<: *other-receivers
+        - 'yardstick':
+            <<: *danube
+            <<: *other-receivers
+
+    jobs:
+        - '{project}-docker-build-push-{stream}'
+
+
+- project:
+
+    name: opnfv-monitor-docker        # projects which only monitor dedicated file or path
+
+    project:
+        # projects with jobs for master
+        - 'daisy':
+            <<: *master
+        - 'escalator':
+            <<: *master
+
+    jobs:
+        - '{project}-docker-build-push-monitor-{stream}'
+
+########################
+# job templates
+########################
+- job-template:
+    name: '{project}-docker-build-push-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters: &parameters
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+        - string:
+            name: PUSH_IMAGE
+            default: "true"
+            description: "To enable/disable pushing the image to Dockerhub."
+        - string:
+            name: DOCKER_REPO_NAME
+            default: "opnfv/{project}"
+            description: "Dockerhub repo to be pushed to."
+        - string:
+            name: RELEASE_VERSION
+            default: ""
+            description: "Release version, e.g. 1.0, 2.0, 3.0"
+        - string:
+            name: DOCKERFILE
+            default: "Dockerfile"
+            description: "Dockerfile to use for creating the image."
+
+    scm:
+        - git-scm
+
+    builders: &builders
+        - shell:
+            !include-raw-escape: ./opnfv-docker.sh
+
+    triggers:
+        - pollscm:
+            cron: "*/30 * * * *"
+
+    publishers:
+        - email:
+            recipients: '{receivers}'
+
+- job-template:
+    name: '{project}-docker-build-push-monitor-{stream}'
+    disabled: '{obj:disabled}'
+    parameters: *parameters
+    scm:
+        - git-scm
+    builders: *builders
+
+    # trigger only matching the file name
+    triggers:
+        - gerrit:
+            trigger-on:
+                - change-merged-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'remerge'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                    - branch-compare-type: 'ANT'
+                      branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: 'docker/**'
+
similarity index 87%
rename from jjb/opnfv/opnfv-docs.yml
rename to jjb/releng/opnfv-docs.yml
index 307c1db..f4b2501 100644 (file)
             doc-version: ''
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
-            doc-version: '2.0'
+            doc-version: '3.0'
             gs-pathname: '/{stream}/{doc-version}'
-            disabled: false
+            disabled: true
 
 ########################
 # job templates
     parameters:
         - project-parameter:
             project: $GERRIT_PROJECT
-        - gerrit-parameter:
             branch: '{branch}'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -79,7 +76,6 @@
     parameters:
         - project-parameter:
             project: $GERRIT_PROJECT
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GS_URL
             description: "JJB configured GERRIT_REFSPEC parameter"
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
diff --git a/jjb/releng/opnfv-lint.yml b/jjb/releng/opnfv-lint.yml
new file mode 100644 (file)
index 0000000..166aea8
--- /dev/null
@@ -0,0 +1,117 @@
+########################
+# Job configuration for opnfv-lint
+########################
+- project:
+
+    name: opnfv-lint
+
+    project: opnfv-lint
+
+    jobs:
+        - 'opnfv-lint-verify-{stream}'
+        - 'opnfv-yamllint-verify-{stream}'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+
+########################
+# job templates
+########################
+
+- job-template:
+    name: 'opnfv-lint-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters:
+        - project-parameter:
+            project: $GERRIT_PROJECT
+            branch: '{branch}'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'REG_EXP'
+                project-pattern: 'functest|sdnvpn|qtip|daisy|sfc|escalator|releng'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: '**/*.py'
+
+    builders:
+        - lint-python-code
+        - report-lint-result-to-gerrit
+
+- job-template:
+    name: 'opnfv-yamllint-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: true
+
+    parameters:
+        - project-parameter:
+            project: $GERRIT_PROJECT
+            branch: '{branch}'
+        - node:
+            name: SLAVE_NAME
+            description: Slaves to execute yamllint
+            default-slaves:
+                - lf-build1
+            allowed-multiselect: true
+            ignore-offline-nodes: true
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'REG_EXP'
+                project-pattern: ''
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: '**/*.yml'
+                  - compare-type: ANT
+                    pattern: '**/*.yaml'
+
+    builders:
+        - lint-yaml-code
+        - report-lint-result-to-gerrit
index 2d88449..ecc8730 100644 (file)
@@ -1,28 +1,25 @@
 - project:
-    name: builder-jobs
+    name: releng-builder-jobs
     jobs:
-        - 'builder-verify-jjb'
-        - 'builder-merge'
-        - 'artifacts-api'
+        - 'releng-verify-jjb'
+        - 'releng-merge-jjb'
+        - 'releng-generate-artifacts-api'
 
     project: 'releng'
 
 - job-template:
-    name: builder-verify-jjb
+    name: releng-verify-jjb
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: 'master'
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -57,7 +54,7 @@
             artifacts: 'job_output/*'
 
 - job-template:
-    name: 'builder-merge'
+    name: 'releng-merge-jjb'
 
     # builder-merge job to run JJB update
     #
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: 'master'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
                 jenkins-jobs update -r --delete-old jjb/
 
 - job-template:
-    name: 'artifacts-api'
+    name: 'releng-generate-artifacts-api'
 
     # Generate and upload the JSON file to used for artifacts site
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: 'master'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - timed: '@hourly'
diff --git a/jjb/releng/testapi-automate.yml b/jjb/releng/testapi-automate.yml
new file mode 100644 (file)
index 0000000..8f3ae0c
--- /dev/null
@@ -0,0 +1,274 @@
+- project:
+    name: testapi-automate
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+
+    phase:
+        - 'docker-update'
+        - 'docker-deploy':
+            slave-label: 'testresults'
+        - 'generate-doc'
+
+    jobs:
+        - 'testapi-automate-{stream}'
+        - 'testapi-automate-{phase}-{stream}'
+        - 'testapi-verify-{stream}'
+
+    project: 'releng'
+
+- job:
+    name: 'testapi-mongodb-backup'
+
+    parameters:
+        - label:
+            name: SLAVE_LABEL
+            default: 'testresults'
+            description: 'Slave label on Jenkins'
+        - project-parameter:
+            project: 'releng'
+            branch: 'master'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/releng
+            description: 'Git URL to use on this Jenkins Slave'
+
+    scm:
+        - git-scm
+
+    triggers:
+        - timed: '@weekly'
+
+    builders:
+        - mongodb-backup
+
+- job-template:
+    name: 'testapi-verify-{stream}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: 'ANT'
+                    pattern: 'utils/test/testapi/**'
+
+    builders:
+        - run-unit-tests
+
+    publishers:
+        - junit:
+            results: nosetests.xml
+        - cobertura:
+            report-file: "coverage.xml"
+            only-stable: "true"
+            health-auto-update: "false"
+            stability-auto-update: "false"
+            zoom-coverage-chart: "true"
+            targets:
+                - files:
+                    healthy: 10
+                    unhealthy: 20
+                    failing: 30
+                - method:
+                    healthy: 50
+                    unhealthy: 40
+                    failing: 30
+
+- job-template:
+    name: 'testapi-automate-{stream}'
+
+    project-type: multijob
+
+    properties:
+        - throttle:
+            enabled: true
+            max-total: 1
+            max-per-node: 1
+            option: 'project'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - string:
+            name: DOCKER_TAG
+            default: "latest"
+            description: "Tag name for testapi docker image"
+        - 'opnfv-build-defaults'
+
+    scm:
+        - git-scm
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 360
+            fail: true
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - change-merged-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'remerge'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: 'ANT'
+                    pattern: 'utils/test/testapi/**'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - multijob:
+            name: docker-update
+            condition: SUCCESSFUL
+            projects:
+                - name: 'testapi-automate-docker-update-{stream}'
+                  current-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: docker-deploy
+            condition: SUCCESSFUL
+            projects:
+                - name: 'testapi-automate-docker-deploy-{stream}'
+                  current-parameters: false
+                  predefined-parameters: |
+                    GIT_BASE=$GIT_BASE
+                  node-label-name: SLAVE_LABEL
+                  node-label: testresults
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+        - multijob:
+            name: generate-doc
+            condition: SUCCESSFUL
+            projects:
+                - name: 'testapi-automate-generate-doc-{stream}'
+                  current-parameters: true
+                  kill-phase-on: FAILURE
+                  abort-all-job: true
+
+    publishers:
+        - 'email-publisher'
+
+- job-template:
+    name: 'testapi-automate-{phase}-{stream}'
+
+    properties:
+        - throttle:
+            enabled: true
+            max-per-node: 1
+            option: 'project'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - string:
+            name: DOCKER_TAG
+            default: "latest"
+            description: "Tag name for testapi docker image"
+
+    wrappers:
+        - ssh-agent-wrapper
+        - timeout:
+            timeout: 120
+            fail: true
+
+    scm:
+        - git-scm
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - 'testapi-automate-{phase}-macro'
+
+################################
+# job builders
+################################
+- builder:
+    name: mongodb-backup
+    builders:
+        - shell: |
+            bash ./jjb/releng/testapi-backup-mongodb.sh
+
+- builder:
+    name: 'run-unit-tests'
+    builders:
+        - shell: |
+            bash ./utils/test/testapi/run_test.sh
+
+- builder:
+    name: 'testapi-automate-docker-update-macro'
+    builders:
+        - shell: |
+            bash ./jjb/releng/testapi-docker-update.sh
+
+- builder:
+    name: 'testapi-automate-generate-doc-macro'
+    builders:
+        - 'testapi-doc-build'
+        - 'upload-doc-artifact'
+
+- builder:
+    name: 'testapi-doc-build'
+    builders:
+        - shell: |
+            bash ./utils/test/testapi/htmlize/doc-build.sh
+
+- builder:
+    name: 'upload-doc-artifact'
+    builders:
+        - shell: |
+            bash ./utils/test/testapi/htmlize/push-doc-artifact.sh
+
+- builder:
+    name: 'testapi-automate-docker-deploy-macro'
+    builders:
+        - shell: |
+            echo 'disable TestAPI update temporarily due to frequent change'
+#            bash ./jjb/releng/testapi-docker-deploy.sh
+
+################################
+# job publishers
+################################
+
+- publisher:
+    name: 'email-publisher'
+    publishers:
+        - email:
+            recipients: rohitsakala@gmail.com feng.xiaowei@zte.com.cn
+            notify-every-unstable-build: false
+            send-to-individuals: true
diff --git a/jjb/releng/testapi-backup-mongodb.sh b/jjb/releng/testapi-backup-mongodb.sh
new file mode 100644 (file)
index 0000000..795e479
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+set -e
+
+# Run MongoDB backup
+python $WORKSPACE/utils/test/testapi/update/templates/backup_mongodb.py -o $WORKSPACE/
+
+# Compressing the dump
+now=$(date +"%m_%d_%Y_%H_%M_%S")
+echo $now
+
+file_name="testapi_mongodb_"$now".tar.gz"
+echo $file_name
+
+tar cvfz "$file_name" test_results_collection*
+
+rm -rf test_results_collection*
+
+artifact_dir="testapibackup"
+workspace="$WORKSPACE"
+
+set +e
+/usr/local/bin/gsutil &>/dev/null
+if [ $? != 0 ]; then
+    echo "Not possible to push results to artifact: gsutil not installed"
+    exit 1
+else
+    echo "Uploading mongodump to artifact $artifact_dir"
+    /usr/local/bin/gsutil cp -r "$workspace"/"$file_name" gs://artifacts.opnfv.org/"$artifact_dir"/
+    echo "MongoDump can be found at http://artifacts.opnfv.org/$artifact_dir.html"
+fi
diff --git a/jjb/releng/testapi-docker-deploy.sh b/jjb/releng/testapi-docker-deploy.sh
new file mode 100644 (file)
index 0000000..b4e60b0
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+function check() {
+
+    # Verify hosted
+    sleep 5
+    cmd=`curl -s --head  --request GET http://testresults.opnfv.org/test/swagger/spec | grep '200 OK' > /dev/null`
+    rc=$?
+    echo $rc
+
+    if [[ $rc == 0 ]]
+    then
+        return 0
+    else
+        return 1
+    fi
+
+}
+
+echo "Getting contianer Id of the currently running one"
+contId=$(sudo docker ps | grep "opnfv/testapi:latest" | awk '{print $1}')
+
+echo "Pulling the latest image"
+sudo docker pull opnfv/testapi:latest
+
+echo "Deleting old containers of opnfv/testapi:old"
+sudo docker ps -a | grep "opnfv/testapi" | grep "old" | awk '{print $1}' | xargs -r sudo docker rm -f
+
+echo "Deleting old images of opnfv/testapi:latest"
+sudo docker images | grep "opnfv/testapi" | grep "old" | awk '{print $3}' | xargs -r sudo docker rmi -f
+
+
+if [[ -z "$contId" ]]
+then
+    echo "No running testapi container"
+
+    echo "Removing stopped testapi containers in the previous iterations"
+    sudo docker ps -f status=exited | grep "opnfv_testapi" | awk '{print $1}' | xargs -r sudo docker rm -f
+else
+    echo $contId
+
+    echo "Get the image id of the currently running conatiner"
+    currImgId=$(sudo docker ps | grep "$contId" | awk '{print $2}')
+    echo $currImgId
+
+    if [[ -z "$currImgId" ]]
+    then
+        echo "No image id found for the container id"
+        exit 1
+    fi
+
+    echo "Changing current image tag to old"
+    sudo docker tag "$currImgId" opnfv/testapi:old
+
+    echo "Removing stopped testapi containers in the previous iteration"
+    sudo docker ps -f status=exited | grep "opnfv_testapi" | awk '{print $1}' | xargs -r sudo docker rm -f
+
+    echo "Renaming the running container name to opnfv_testapi as to identify it."
+    sudo docker rename $contId opnfv_testapi
+
+    echo "Stop the currently running container"
+    sudo docker stop $contId
+fi
+
+echo "Running a container with the new image"
+sudo docker run -dti -p "8082:8000" -e "mongodb_url=mongodb://172.17.0.1:27017" -e "swagger_url=http://testresults.opnfv.org/test" opnfv/testapi:latest
+
+if check; then
+    echo "TestResults Hosted."
+else
+    echo "TestResults Hosting Failed"
+    if [[ $(sudo docker images | grep "opnfv/testapi" | grep "old" | awk '{print $3}') ]]; then
+        echo "Running old Image"
+        sudo docker run -dti -p "8082:8000" -e "mongodb_url=mongodb://172.17.0.1:27017" -e "swagger_url=http://testresults.opnfv.org/test" opnfv/testapi:old
+        exit 1
+    fi
+fi
+
+# Echo Images and Containers
+sudo docker images
+sudo docker ps -a
diff --git a/jjb/releng/testapi-docker-update.sh b/jjb/releng/testapi-docker-update.sh
new file mode 100644 (file)
index 0000000..84f5c32
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -o errexit
+set -o nounset
+
+cd $WORKSPACE/utils/test/testapi/docker/
+
+# Remove previous containers
+docker ps -a | grep "opnfv/testapi" | awk '{ print $1 }' | xargs -r docker rm -f
+
+# Remove previous images
+docker images | grep "opnfv/testapi" | awk '{ print $3 }' | xargs -r docker rmi -f
+
+# Start build
+docker build --no-cache -t opnfv/testapi:$DOCKER_TAG .
+
+# Push Image
+docker push opnfv/testapi:$DOCKER_TAG
index 5c2dbff..682a8be 100755 (executable)
@@ -8,7 +8,7 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 #test for non-ascii characters, these can pass the test and end up breaking things in production
-for x in $(find . -name *\.yml); do
+for x in $(find . -name *\.yml -or -name *\.yaml); do
 
   if LC_ALL=C grep -q '[^[:print:][:space:]]' "$x"; then
     echo "file "$x" contains non-ascii characters"
diff --git a/jjb/securityaudit/opnfv-security-audit.yml b/jjb/securityaudit/opnfv-security-audit.yml
new file mode 100644 (file)
index 0000000..732df89
--- /dev/null
@@ -0,0 +1,105 @@
+########################
+# Job configuration for opnfv-lint
+########################
+- project:
+
+    name: anteaterfw
+
+    project: anteaterfw
+
+    jobs:
+        - 'opnfv-security-audit-verify-{stream}'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+
+########################
+# job templates
+########################
+- job-template:
+    name: 'opnfv-security-audit-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters:
+        - project-parameter:
+            project: $GERRIT_PROJECT
+            branch: '{branch}'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'REG_EXP'
+                project-pattern: 'sandbox'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: '**/*.py'
+          skip-vote:
+            successful: true
+            failed: true
+            unstable: true
+            notbuilt: true
+
+    builders:
+        - security-audit-python-code
+        - report-security-audit-result-to-gerrit
+########################
+# builder macros
+########################
+- builder:
+    name: security-audit-python-code
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o pipefail
+            set -o xtrace
+            export PATH=$PATH:/usr/local/bin/
+
+            # this is where the security/license audit script will be executed
+            echo "Hello World!"
+- builder:
+    name: report-security-audit-result-to-gerrit
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o pipefail
+            set -o xtrace
+            export PATH=$PATH:/usr/local/bin/
+
+            # If no violations were found, no lint log will exist.
+            if [[ -e securityaudit.log ]] ; then
+                echo -e "\nposting security audit report to gerrit...\n"
+
+                cat securityaudit.log
+                echo
+
+                ssh -p 29418 gerrit.opnfv.org \
+                    "gerrit review -p $GERRIT_PROJECT \
+                     -m \"$(cat securityaudit.log)\" \
+                     $GERRIT_PATCHSET_REVISION \
+                     --notify NONE"
+
+                exit 1
+            fi
similarity index 75%
rename from jjb/qtip/qtip-project-jobs.yml
rename to jjb/snaps/snaps.yml
index 722a9be..50b7c30 100644 (file)
@@ -1,42 +1,42 @@
+###################################################
+# All the jobs except verify have been removed!
+# They will only be enabled on request by projects!
+###################################################
 - project:
-    name: qtip
+    name: snaps
 
     project: '{name}'
 
     jobs:
-        - 'qtip-verify-{stream}'
+        - 'snaps-verify-{stream}'
 
-# only master branch is enabled at the moment to keep no of jobs sane
     stream:
         - master:
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
 
 - job-template:
-    name: 'qtip-verify-{stream}'
+    name: 'snaps-verify-{stream}'
 
     disabled: '{obj:disabled}'
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -53,6 +53,7 @@
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**|.gitignore'
index 026b643..709a1eb 100644 (file)
@@ -13,7 +13,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
@@ -28,7 +28,6 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - string:
             name: GIT_BASE
             description: "Used for overriding the GIT URL coming from Global Jenkins configuration in case if the stuff is done on none-LF HW."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
-            branch: 'master'
+            branch: '{branch}'
         - string:
             name: GIT_BASE
             default: https://gerrit.opnfv.org/gerrit/$PROJECT
             description: "Used for overriding the GIT URL coming from Global Jenkins configuration in case if the stuff is done on none-LF HW."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
     # Required Variables:
     #     stream:    branch with - in place of / (eg. stable)
     #     branch:    branch (eg. stable)
-    node: opnfv-build-ubuntu
-
-    disabled: true
+    disabled: '{obj:disabled}'
 
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
+        - 'intel-pod9-defaults'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - timed: 'H H * * *'
diff --git a/jjb/test-requirements.txt b/jjb/test-requirements.txt
new file mode 100644 (file)
index 0000000..6b700dc
--- /dev/null
@@ -0,0 +1 @@
+jenkins-job-builder
diff --git a/jjb/ves/ves.yml b/jjb/ves/ves.yml
new file mode 100644 (file)
index 0000000..e6243f3
--- /dev/null
@@ -0,0 +1,69 @@
+###################################################
+# All the jobs except verify have been removed!
+# They will only be enabled on request by projects!
+###################################################
+- project:
+    name: ves
+
+    project: '{name}'
+
+    jobs:
+        - 'ves-verify-{stream}'
+
+    stream:
+        - master:
+            branch: '{stream}'
+            gs-pathname: ''
+            disabled: false
+        - danube:
+            branch: 'stable/{stream}'
+            gs-pathname: '/{stream}'
+            disabled: false
+
+- job-template:
+    name: 'ves-verify-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{branch}'
+        - 'opnfv-build-ubuntu-defaults'
+
+    scm:
+        - git-scm-gerrit
+
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                - patchset-created-event:
+                    exclude-drafts: 'false'
+                    exclude-trivial-rebase: 'false'
+                    exclude-no-code-change: 'false'
+                - draft-published-event
+                - comment-added-contains-event:
+                    comment-contains-value: 'recheck'
+                - comment-added-contains-event:
+                    comment-contains-value: 'reverify'
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: '{project}'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'docs/**|.gitignore'
+
+    builders:
+        - shell: |
+            #!/bin/bash
+            set -o errexit
+            set -o nounset
+            set -o pipefail
+
+           # shellcheck -f tty tests/*.sh
+           # shellcheck -f tty utils/*.sh
index c988c06..450599e 100644 (file)
@@ -11,7 +11,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
index 3f7f6bf..c5c81c8 100644 (file)
             gs-pathname: ''
             disabled: false
             slave-label: 'opnfv-build-ubuntu'
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
-            slave-label: 'intel-pod3'
+            slave-label: 'opnfv-build-ubuntu'
 
 - job-template:
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - 'intel-pod3-defaults'
+            branch: '{branch}'
+        - 'intel-pod12-defaults'
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     triggers:
         - pollscm:
@@ -47,7 +45,7 @@
             pwd
             cd src
             make clobber
-            make
+            make MORE_MAKE_FLAGS="-j 10"
             # run basic sanity test
             make sanity
             cd ../ci
     concurrent: true
 
     properties:
-        - throttle:
-            enabled: true
-            max-total: 3
-            max-per-node: 2
-            option: 'project'
+        - logrotate-default
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'vswitchperf-verify-.*'
+                - 'vswitchperf-merge-.*'
+            block-level: 'NODE'
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
             pwd
             cd src
             make clobber
-            make
+            make MORE_MAKE_FLAGS="-j 5"
             # run basic sanity test
             make sanity
             cd ../ci
     concurrent: true
 
     properties:
-        - throttle:
-            enabled: true
-            max-total: 3
-            max-per-node: 2
-            option: 'project'
+        - logrotate-default
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - 'vswitchperf-verify-.*'
+                - 'vswitchperf-merge-.*'
+            block-level: 'NODE'
 
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - '{slave-label}-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
                 branches:
                     - branch-compare-type: 'ANT'
                       branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'docs/**'
             pwd
             cd src
             make clobber
-            make
+            make MORE_MAKE_FLAGS="-j 5"
             cd ../ci
             ./build-vsperf.sh merge
diff --git a/jjb/xci/bifrost-cleanup-job.yml b/jjb/xci/bifrost-cleanup-job.yml
new file mode 100644 (file)
index 0000000..d5a444d
--- /dev/null
@@ -0,0 +1,141 @@
+- project:
+    name: 'openstack-bifrost-cleanup'
+#--------------------------------
+# branches
+#--------------------------------
+    stream:
+        - master:
+            branch: '{stream}'
+
+#--------------------------------
+# projects
+#--------------------------------
+    project:
+        - 'openstack':
+            project-repo: 'https://git.openstack.org/openstack/bifrost'
+            clone-location: '/opt/bifrost'
+        - 'opnfv':
+            project-repo: 'https://gerrit.opnfv.org/gerrit/releng'
+            clone-location: '/opt/releng'
+
+#--------------------------------
+# jobs
+#--------------------------------
+    jobs:
+        - '{project}-bifrost-cleanup-{stream}'
+
+- job-template:
+    name: '{project}-bifrost-cleanup-{stream}'
+
+    concurrent: false
+
+    node: bifrost-verify-virtual
+
+    # Make sure no verify job is running on any of the slaves since that would
+    # produce build logs after we wipe the destination directory.
+    properties:
+        - build-blocker:
+            blocking-jobs:
+                - '{project}-bifrost-verify-*'
+
+    parameters:
+        - string:
+            name: PROJECT
+            default: '{project}'
+
+    builders:
+        - shell: |
+            #!/bin/bash
+
+            set -eu
+
+            # DO NOT change this unless you know what you are doing.
+            BIFROST_GS_URL="gs://artifacts.opnfv.org/cross-community-ci/openstack/bifrost/$GERRIT_NAME/$GERRIT_CHANGE_NUMBER/"
+
+            # This should never happen... even 'recheck' uses the last jobs'
+            # gerrit information. Better exit with error so we can investigate
+            [[ ! -n $GERRIT_NAME ]] || [[ ! -n $GERRIT_CHANGE_NUMBER ]] && exit 1
+
+            echo "Removing build artifacts for $GERRIT_NAME/$GERRIT_CHANGE_NUMBER"
+
+            if ! [[ "$BIFROST_GS_URL" =~ "/cross-community-ci/openstack/bifrost/" ]]; then
+                echo "Oops! BIFROST_GS_URL=$BIFROST_GS_URL does not seem like a valid"
+                echo "bifrost location on the Google storage server. Please double-check"
+                echo "that it's set properly or fix this line if necessary."
+                echo "gsutil will not be executed until this is fixed!"
+                exit 1
+            fi
+            try_to_rm=1
+            while [[ $try_to_rm -lt 6 ]]; do
+                gsutil -m rm -r $BIFROST_GS_URL && _exitcode=$? && break
+                _exitcode=$?
+                echo "gsutil rm failed! Trying again... (attempt #$try_to_rm)"
+                let try_to_rm += 1
+                # Give it some time...
+                sleep 10
+            done
+            exit $_exitcode
+
+    triggers:
+        - '{project}-gerrit-trigger-cleanup':
+            branch: '{branch}'
+
+    publishers:
+        - email:
+            recipients: fatih.degirmenci@ericsson.com yroblamo@redhat.com mchandras@suse.de jack.morgan@intel.com zhang.jun3g@zte.com.cn
+#--------------------------------
+# trigger macros
+#--------------------------------
+- trigger:
+    name: 'openstack-gerrit-trigger-cleanup'
+    triggers:
+        - gerrit:
+            server-name: 'review.openstack.org'
+            escape-quotes: true
+            trigger-on:
+                # We only run this when the change is merged or
+                # abandoned since we don't need the logs anymore
+                - change-merged-event
+                - change-abandoned-event
+            # This is an OPNFV maintenance job. We don't want to provide
+            # feedback on Gerrit
+            silent: true
+            silent-start: true
+            projects:
+              - project-compare-type: 'PLAIN'
+                project-pattern: 'openstack/bifrost'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
+                forbidden-file-paths:
+                  - compare-type: ANT
+                    pattern: 'doc/**'
+                  - compare-type: ANT
+                    pattern: 'releasenotes/**'
+                disable-strict-forbidden-file-verification: 'true'
+            readable-message: true
+- trigger:
+    name: 'opnfv-gerrit-trigger-cleanup'
+    triggers:
+        - gerrit:
+            server-name: 'gerrit.opnfv.org'
+            trigger-on:
+                # We only run this when the change is merged or
+                # abandoned since we don't need the logs anymore
+                - change-merged-event
+                - change-abandoned-event
+            # This is an OPNFV maintenance job. We don't want to provide
+            # feedback on Gerrit
+            silent: true
+            silent-start: true
+            projects:
+              - project-compare-type: 'ANT'
+                project-pattern: 'releng'
+                branches:
+                  - branch-compare-type: 'ANT'
+                    branch-pattern: '**/{branch}'
+                file-paths:
+                  - compare-type: ANT
+                    pattern: 'prototypes/bifrost/**'
+            readable-message: true
diff --git a/jjb/xci/bifrost-periodic-jobs.yml b/jjb/xci/bifrost-periodic-jobs.yml
new file mode 100644 (file)
index 0000000..3e9ff67
--- /dev/null
@@ -0,0 +1,152 @@
+- project:
+    project: 'releng'
+
+    name: 'bifrost-periodic'
+#--------------------------------
+# Branch Anchors
+#--------------------------------
+# the versions stated here default to branches which then later
+# on used for checking out the branches, pulling in head of the branch.
+    master: &master
+        stream: master
+        openstack-bifrost-version: '{stream}'
+        opnfv-releng-version: 'master'
+        gs-pathname: ''
+    ocata: &ocata
+        stream: ocata
+        openstack-bifrost-version: 'stable/{stream}'
+        opnfv-releng-version: 'master'
+        gs-pathname: '/{stream}'
+#--------------------------------
+#        XCI PODs
+#--------------------------------
+    pod:
+        - virtual:
+            <<: *master
+        - virtual:
+            <<: *ocata
+#--------------------------------
+# XCI PODs
+#--------------------------------
+#--------------------------------
+# Supported Distros
+#--------------------------------
+    distro:
+        - 'xenial':
+            disabled: false
+            slave-label: xci-xenial-virtual
+            dib-os-release: 'xenial'
+            dib-os-element: 'ubuntu-minimal'
+            dib-os-packages: 'vlan,vim,less,bridge-utils,sudo,language-pack-en,iputils-ping,rsyslog,curl,python,debootstrap,ifenslave,ifenslave-2.6,lsof,lvm2,tcpdump,nfs-kernel-server,chrony,iptables'
+            extra-dib-elements: 'openssh-server'
+        - 'centos7':
+            disabled: true
+            slave-label: xci-centos7-virtual
+            dib-os-release: '7'
+            dib-os-element: 'centos7'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
+        - 'suse':
+            disabled: true
+            slave-label: xci-suse-virtual
+            dib-os-release: '42.2'
+            dib-os-element: 'opensuse-minimal'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
+
+#--------------------------------
+# jobs
+#--------------------------------
+    jobs:
+        - 'bifrost-provision-{pod}-{distro}-periodic-{stream}'
+
+#--------------------------------
+# job templates
+#--------------------------------
+- job-template:
+    name: 'bifrost-provision-{pod}-{distro}-periodic-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - '^xci-os.*'
+                - '^xci-deploy.*'
+                - '^xci-functest.*'
+                - '^bifrost-.*periodic.*'
+                - '^osa-.*periodic.*'
+            block-level: 'NODE'
+        - logrotate-default
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{opnfv-releng-version}'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+        - string:
+            name: XCI_FLAVOR
+            default: 'ha'
+        - string:
+            name: OPENSTACK_BIFROST_VERSION
+            default: '{openstack-bifrost-version}'
+        - string:
+            name: OPNFV_RELENG_VERSION
+            default: '{opnfv-releng-version}'
+        - string:
+            name: DISTRO
+            default: '{distro}'
+        - string:
+            name: DIB_OS_RELEASE
+            default: '{dib-os-release}'
+        - string:
+            name: DIB_OS_ELEMENT
+            default: '{dib-os-element}'
+        - string:
+            name: DIB_OS_PACKAGES
+            default: '{dib-os-packages}'
+        - string:
+            name: EXTRA_DIB_ELEMENTS
+            default: '{extra-dib-elements}'
+        - string:
+            name: CLEAN_DIB_IMAGES
+            default: 'true'
+        - label:
+            name: SLAVE_LABEL
+            default: '{slave-label}'
+        - string:
+            name: ANSIBLE_VERBOSITY
+            default: ''
+        - string:
+            name: XCI_LOOP
+            default: 'periodic'
+
+    wrappers:
+        - fix-workspace-permissions
+
+    scm:
+        - git-scm
+
+    # trigger is disabled until we know which jobs we will have
+    # and adjust stuff accordingly
+    triggers:
+        - timed: '#@midnight'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME - Flavor: $XCI_FLAVOR"
+        - 'bifrost-provision-builder'
+
+#---------------------------
+# builder macros
+#---------------------------
+- builder:
+    name: bifrost-provision-builder
+    builders:
+        - shell:
+            !include-raw: ./bifrost-provision.sh
diff --git a/jjb/xci/bifrost-provision.sh b/jjb/xci/bifrost-provision.sh
new file mode 100755 (executable)
index 0000000..4724c2e
--- /dev/null
@@ -0,0 +1,109 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o nounset
+set -o pipefail
+
+trap cleanup_and_upload EXIT
+
+function fix_ownership() {
+    if [ -z "${JOB_URL+x}" ]; then
+        echo "Not running as part of Jenkins. Handle the logs manually."
+    else
+        # Make sure cache exists
+        [[ ! -d ${HOME}/.cache ]] && mkdir ${HOME}/.cache
+
+        sudo chown -R jenkins:jenkins $WORKSPACE
+        sudo chown -R jenkins:jenkins ${HOME}/.cache
+    fi
+}
+
+function cleanup_and_upload() {
+    original_exit=$?
+    fix_ownership
+    exit $original_exit
+}
+
+# check distro to see if we support it
+if [[ ! "$DISTRO" =~ (xenial|centos7|suse) ]]; then
+    echo "Distro $DISTRO is not supported!"
+    exit 1
+fi
+
+# remove previously cloned repos
+sudo /bin/rm -rf /opt/bifrost /opt/openstack-ansible /opt/releng /opt/functest
+
+# Fix up permissions
+fix_ownership
+
+# ensure the versions to checkout are set
+export OPENSTACK_BIFROST_VERSION=${OPENSTACK_BIFROST_VERSION:-master}
+export OPNFV_RELENG_VERSION=${OPNFV_RELENG_VERSION:-master}
+
+# log some info
+echo -e "\n"
+echo "***********************************************************************"
+echo "*                                                                     *"
+echo "*                      Provision OpenStack Nodes                      *"
+echo "*                                                                     *"
+echo "                       bifrost version: $OPENSTACK_BIFROST_VERSION"
+echo "                       releng version: $OPNFV_RELENG_VERSION"
+echo "*                                                                     *"
+echo "***********************************************************************"
+echo -e "\n"
+
+# clone the repos and checkout the versions
+sudo git clone --quiet https://git.openstack.org/openstack/bifrost /opt/bifrost
+cd /opt/bifrost && sudo git checkout --quiet $OPENSTACK_BIFROST_VERSION
+echo "xci: using bifrost commit"
+git show --oneline -s --pretty=format:'%h - %s (%cr) <%an>'
+
+sudo git clone --quiet https://gerrit.opnfv.org/gerrit/releng /opt/releng
+cd /opt/releng && sudo git checkout --quiet $OPNFV_RELENG_VERSION
+echo "xci: using releng commit"
+git show --oneline -s --pretty=format:'%h - %s (%cr) <%an>'
+
+# source flavor vars
+source "$WORKSPACE/prototypes/xci/config/${XCI_FLAVOR}-vars"
+
+# combine opnfv and upstream scripts/playbooks
+sudo /bin/cp -rf /opt/releng/prototypes/bifrost/* /opt/bifrost/
+
+# cleanup remnants of previous deployment
+cd /opt/bifrost
+sudo -E ./scripts/destroy-env.sh
+
+# provision VMs for the flavor
+cd /opt/bifrost
+sudo -E ./scripts/bifrost-provision.sh
+
+# list the provisioned VMs
+cd /opt/bifrost
+source env-vars
+ironic node-list
+virsh list
+
+echo "OpenStack nodes are provisioned!"
+# here we have to do something in order to capture what was the working sha1
+# hardcoding stuff for the timebeing
+
+cd /opt/bifrost
+BIFROST_GIT_SHA1=$(git rev-parse HEAD)
+
+# log some info
+echo -e "\n"
+echo "***********************************************************************"
+echo "*                       BIFROST SHA1 TO PIN                           *"
+echo "*                                                                     *"
+echo "    $BIFROST_GIT_SHA1"
+echo "*                                                                     *"
+echo "***********************************************************************"
+
+echo -e "\n"
similarity index 64%
rename from jjb/infra/bifrost-verify-jobs.yml
rename to jjb/xci/bifrost-verify-jobs.yml
index 751aa0c..8068296 100644 (file)
             disabled: false
             dib-os-release: 'trusty'
             dib-os-element: 'ubuntu-minimal'
-            dib-os-packages: 'openssh-server,vlan,vim,less,bridge-utils,language-pack-en,iputils-ping,rsyslog,curl'
+            dib-os-packages: 'vlan,vim,less,bridge-utils,language-pack-en,iputils-ping,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
         - 'centos7':
             disabled: false
             dib-os-release: '7'
             dib-os-element: 'centos7'
-            dib-os-packages: 'openssh-server,vim,less,bridge-utils,iputils,rsyslog,curl'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
         - 'suse':
-            disabled: true
-            dib-os-release: 'suse'
-            dib-os-element: 'suse'
-            dib-os-packages: ''
+            disabled: false
+            dib-os-release: '42.2'
+            dib-os-element: 'opensuse-minimal'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
 #--------------------------------
 # type
 #--------------------------------
 #--------------------------------
     jobs:
         - '{project}-bifrost-verify-{distro}-{type}-{stream}'
+
+#--------------------------------
+# VM defaults
+#--------------------------------
+- defaults:
+    name: verify_vm_defaults
+    test-vm-num-nodes: '3'
+    test-vm-node-names: 'opnfv controller00 compute00'
+    vm-domain-type: 'kvm'
+    vm-cpu: '2'
+    vm-disk: '30'
+    vm-memory-size: '4096'
+    vm-disk-cache: 'unsafe'
+
 #--------------------------------
 # job templates
 #--------------------------------
 
     disabled: '{obj:disabled}'
 
+    defaults: verify_vm_defaults
+
     concurrent: false
 
     properties:
+        - logrotate-default
         - build-blocker:
             use-build-blocker: true
             blocking-jobs:
         - string:
             name: DIB_OS_ELEMENT
             default: '{dib-os-element}'
+        - string:
+            name: EXTRA_DIB_ELEMENTS
+            default: '{extra-dib-elements}'
         - string:
             name: DIB_OS_PACKAGES
             default: '{dib-os-packages}'
+        - string:
+            name: TEST_VM_NUM_NODES
+            default: '{test-vm-num-nodes}'
+        - string:
+            name: TEST_VM_NODE_NAMES
+            default: '{test-vm-node-names}'
+        - string:
+            name: VM_DOMAIN_TYPE
+            default: '{vm-domain-type}'
+        - string:
+            name: VM_CPU
+            default: '{vm-cpu}'
+        - string:
+            name: VM_DISK
+            default: '{vm-disk}'
+        - string:
+            name: VM_MEMORY_SIZE
+            default: '{vm-memory-size}'
+        - string:
+            name: VM_DISK_CACHE
+            default: '{vm-disk-cache}'
         - string:
             name: CLEAN_DIB_IMAGES
             default: 'true'
         - label:
             name: SLAVE_LABEL
             default: 'infra-{type}-{distro}'
+        - string:
+            name: BIFROST_LOG_URL
+            default: 'http://artifacts.opnfv.org/cross-community-ci/openstack/bifrost/$GERRIT_NAME/$GERRIT_CHANGE_NUMBER/$GERRIT_PATCHSET_NUMBER/$JOB_NAME'
+        - string:
+            name: ANSIBLE_VERBOSITY
+            default: '-vvvv'
+        - string:
+            name: XCI_LOOP
+            default: 'verify'
 
     scm:
         - git:
             url: '$PROJECT_REPO'
             refspec: '$GERRIT_REFSPEC'
             branches:
-                - 'origin/$GERRIT_BRANCH'
+                - 'origin/$BRANCH'
             skip-tag: true
             choosing-strategy: 'gerrit'
             timeout: 10
             branch: '{branch}'
 
     builders:
-        - description-setter:
-            description: "Built on $NODE_NAME"
-        - shell:
-            !include-raw-escape: ./bifrost-verify.sh
+        - bifrost-set-name
+        - bifrost-build
+
+    wrappers:
+        - fix-workspace-permissions
 
     publishers:
         - email:
-            recipients: fatih.degirmenci@ericsson.com yroblamo@redhat.com mchandras@suse.de jack.morgan@intel.com zhang.jun3g@zte.com.cn
+            recipients: fatih.degirmenci@ericsson.com yroblamo@redhat.com mchandras@suse.de jack.morgan@intel.com julienjut@gmail.com
 #--------------------------------
 # trigger macros
 #--------------------------------
                     exclude-no-code-change: 'false'
                 - comment-added-contains-event:
                     comment-contains-value: 'recheck'
-            custom-url: '* $JOB_NAME $BUILD_URL'
+            custom-url: '* $JOB_NAME $BIFROST_LOG_URL/index.html'
             silent-start: true
             projects:
               - project-compare-type: 'PLAIN'
                 branches:
                   - branch-compare-type: 'ANT'
                     branch-pattern: '**/{branch}'
+                disable-strict-forbidden-file-verification: 'true'
                 forbidden-file-paths:
                   - compare-type: ANT
                     pattern: 'doc/**'
                   - compare-type: ANT
                     pattern: 'releasenotes/**'
+                disable-strict-forbidden-file-verification: 'true'
             readable-message: true
 - trigger:
     name: 'opnfv-gerrit-trigger'
                     comment-contains-value: 'recheck'
                 - comment-added-contains-event:
                     comment-contains-value: 'reverify'
+            custom-url: '* $JOB_NAME $BIFROST_LOG_URL/index.html'
             projects:
               - project-compare-type: 'ANT'
                 project-pattern: 'releng'
                 file-paths:
                   - compare-type: ANT
                     pattern: 'prototypes/bifrost/**'
-                  - compare-type: ANT
-                    pattern: 'jjb/infra/**'
             readable-message: true
+
+#---------------------------
+# builder macros
+#---------------------------
+- builder:
+    name: bifrost-set-name
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+
+- builder:
+    name: bifrost-build
+    builders:
+        - shell:
+            !include-raw: ./bifrost-verify.sh
diff --git a/jjb/xci/bifrost-verify.sh b/jjb/xci/bifrost-verify.sh
new file mode 100755 (executable)
index 0000000..f596d75
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o nounset
+set -o pipefail
+
+trap cleanup_and_upload EXIT
+
+function upload_logs() {
+    BIFROST_CONSOLE_LOG="${BUILD_URL}/consoleText"
+    BIFROST_GS_URL=${BIFROST_LOG_URL/http:/gs:}
+
+    # Make sure the old landing page is gone in case
+    # we break later on. We don't want to publish
+    # stale information.
+    # TODO: Maybe cleanup the entire $BIFROST_GS_URL directory
+    # before we upload the new data.
+    gsutil -q rm ${BIFROST_GS_URL}/index.html || true
+
+    echo "Uploading collected bifrost build logs to ${BIFROST_LOG_URL}"
+
+    if [[ -d ${WORKSPACE}/logs ]]; then
+        pushd ${WORKSPACE}/logs &> /dev/null
+        for x in *.log; do
+            echo "Compressing and uploading $x"
+            gsutil -q cp -Z ${x} ${BIFROST_GS_URL}/${x}
+        done
+        popd &> /dev/null
+    fi
+
+    echo "Generating the ${BIFROST_LOG_URL}/index.html landing page"
+    cat > ${WORKSPACE}/index.html <<EOF
+<html>
+<h1>Build results for <a href=https://$GERRIT_NAME/#/c/$GERRIT_CHANGE_NUMBER/$GERRIT_PATCHSET_NUMBER>$GERRIT_NAME/$GERRIT_CHANGE_NUMBER/$GERRIT_PATCHSET_NUMBER</a></h1>
+<h2>Job: <a href=${BUILD_URL}>$JOB_NAME</a></h2>
+<ul>
+<li><a href=${BIFROST_LOG_URL}/build_log.txt>build_log.txt</a></li>
+EOF
+
+    if [[ -d ${WORKSPACE}/logs ]]; then
+        pushd ${WORKSPACE}/logs &> /dev/null
+        for x in *.log; do
+            echo "<li><a href=${BIFROST_LOG_URL}/${x}>${x}</a></li>" >> ${WORKSPACE}/index.html
+        done
+        popd &> /dev/null
+    fi
+
+    cat >> ${WORKSPACE}/index.html << EOF
+</ul>
+</html>
+EOF
+
+    # Finally, download and upload the entire build log so we can retain
+    # as much build information as possible
+    echo "Uploading the final console output"
+    curl -s -L ${BIFROST_CONSOLE_LOG} > ${WORKSPACE}/build_log.txt
+    gsutil -q cp -Z ${WORKSPACE}/build_log.txt ${BIFROST_GS_URL}/build_log.txt
+    rm ${WORKSPACE}/build_log.txt
+
+    # Upload landing page
+    gsutil -q cp ${WORKSPACE}/index.html ${BIFROST_GS_URL}/index.html
+    rm ${WORKSPACE}/index.html
+}
+
+function fix_ownership() {
+    if [ -z "${JOB_URL+x}" ]; then
+        echo "Not running as part of Jenkins. Handle the logs manually."
+    else
+        # Make sure cache exists
+        [[ ! -d ${HOME}/.cache ]] && mkdir ${HOME}/.cache
+
+        sudo chown -R jenkins:jenkins $WORKSPACE
+        sudo chown -R jenkins:jenkins ${HOME}/.cache
+    fi
+}
+
+function cleanup_and_upload() {
+    original_exit=$?
+    fix_ownership
+    upload_logs
+    exit $original_exit
+}
+
+# check distro to see if we support it
+if [[ ! "$DISTRO" =~ (trusty|centos7|suse) ]]; then
+    echo "Distro $DISTRO is not supported!"
+    exit 1
+fi
+
+# remove previously cloned repos
+sudo /bin/rm -rf /opt/bifrost /opt/releng
+
+# Fix up permissions
+fix_ownership
+
+# clone all the repos first and checkout the patch afterwards
+sudo git clone https://git.openstack.org/openstack/bifrost /opt/bifrost
+sudo git clone https://gerrit.opnfv.org/gerrit/releng /opt/releng
+
+# checkout the patch
+cd $CLONE_LOCATION
+sudo git fetch $PROJECT_REPO $GERRIT_REFSPEC && sudo git checkout FETCH_HEAD
+
+# combine opnfv and upstream scripts/playbooks
+sudo /bin/cp -rf /opt/releng/prototypes/bifrost/* /opt/bifrost/
+
+# cleanup remnants of previous deployment
+cd /opt/bifrost
+sudo -E ./scripts/destroy-env.sh
+
+# provision 3 VMs; xcimaster, controller, and compute
+cd /opt/bifrost
+sudo -E ./scripts/bifrost-provision.sh
+
+# list the provisioned VMs
+cd /opt/bifrost
+source env-vars
+ironic node-list
+virsh list
diff --git a/jjb/xci/osa-periodic-jobs.yml b/jjb/xci/osa-periodic-jobs.yml
new file mode 100644 (file)
index 0000000..56a4b18
--- /dev/null
@@ -0,0 +1,149 @@
+- project:
+    project: 'releng'
+
+    name: 'os-periodic'
+#--------------------------------
+# Branch Anchors
+#--------------------------------
+# the versions stated here default to branches which then later
+# on used for checking out the branches, pulling in head of the branch.
+    master: &master
+        stream: master
+        openstack-osa-version: '{stream}'
+        opnfv-releng-version: 'master'
+        gs-pathname: ''
+    ocata: &ocata
+        stream: ocata
+        openstack-osa-version: 'stable/{stream}'
+        opnfv-releng-version: 'master'
+        gs-pathname: '/{stream}'
+#--------------------------------
+#        XCI PODs
+#--------------------------------
+    pod:
+        - virtual:
+            <<: *master
+        - virtual:
+            <<: *ocata
+#--------------------------------
+# Supported Distros
+#--------------------------------
+    distro:
+        - 'xenial':
+            disabled: false
+            slave-label: xci-xenial-virtual
+            dib-os-release: 'xenial'
+            dib-os-element: 'ubuntu-minimal'
+            dib-os-packages: 'vlan,vim,less,bridge-utils,sudo,language-pack-en,iputils-ping,rsyslog,curl,python,debootstrap,ifenslave,ifenslave-2.6,lsof,lvm2,tcpdump,nfs-kernel-server,chrony,iptables'
+            extra-dib-elements: 'openssh-server'
+        - 'centos7':
+            disabled: true
+            slave-label: xci-centos7-virtual
+            dib-os-release: '7'
+            dib-os-element: 'centos7'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
+        - 'suse':
+            disabled: true
+            slave-label: xci-suse-virtual
+            dib-os-release: '42.2'
+            dib-os-element: 'opensuse-minimal'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
+
+#--------------------------------
+# jobs
+#--------------------------------
+    jobs:
+        - 'osa-deploy-{pod}-{distro}-periodic-{stream}'
+
+#--------------------------------
+# job templates
+#--------------------------------
+- job-template:
+    name: 'osa-deploy-{pod}-{distro}-periodic-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - '^xci-os.*'
+                - '^xci-deploy.*'
+                - '^xci-functest.*'
+                - '^bifrost-.*periodic.*'
+                - '^osa-.*periodic.*'
+            block-level: 'NODE'
+        - logrotate-default
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{opnfv-releng-version}'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+        - string:
+            name: XCI_FLAVOR
+            default: 'ha'
+        - string:
+            name: OPENSTACK_OSA_VERSION
+            default: '{openstack-osa-version}'
+        - string:
+            name: OPNFV_RELENG_VERSION
+            default: '{opnfv-releng-version}'
+        - string:
+            name: DISTRO
+            default: '{distro}'
+        - string:
+            name: DIB_OS_RELEASE
+            default: '{dib-os-release}'
+        - string:
+            name: DIB_OS_ELEMENT
+            default: '{dib-os-element}'
+        - string:
+            name: DIB_OS_PACKAGES
+            default: '{dib-os-packages}'
+        - string:
+            name: EXTRA_DIB_ELEMENTS
+            default: '{extra-dib-elements}'
+        - string:
+            name: CLEAN_DIB_IMAGES
+            default: 'true'
+        - label:
+            name: SLAVE_LABEL
+            default: '{slave-label}'
+        - string:
+            name: ANSIBLE_VERBOSITY
+            default: ''
+        - string:
+            name: XCI_LOOP
+            default: 'periodic'
+
+    wrappers:
+        - fix-workspace-permissions
+
+    scm:
+        - git-scm
+
+    # trigger is disabled until we know which jobs we will have
+    # and adjust stuff accordingly
+    triggers:
+        - timed: '#@midnight'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME - Scenario: $DEPLOY_SCENARIO"
+        - 'osa-deploy-builder'
+
+#---------------------------
+# builder macros
+#---------------------------
+- builder:
+    name: osa-deploy-builder
+    builders:
+        - shell:
+            !include-raw: ./xci-deploy.sh
diff --git a/jjb/xci/xci-daily-jobs.yml b/jjb/xci/xci-daily-jobs.yml
new file mode 100644 (file)
index 0000000..64e13d3
--- /dev/null
@@ -0,0 +1,235 @@
+#--------------------------------
+# These jobs run on a daily basis and deploy OpenStack
+# using the pinned versions of opnfv/releng, openstack/bifrost
+# and openstack/openstack-ansible. Due to this, there is no
+# version/branch is set/passed to jobs and instead the versions
+# are checked out based on what is configured.
+#--------------------------------
+- project:
+    project: 'releng'
+
+    name: 'xci-daily'
+#--------------------------------
+# Branch Anchors
+#--------------------------------
+    master: &master
+        stream: master
+        opnfv-releng-version: master
+        gs-pathname: ''
+    ocata: &ocata
+        stream: ocata
+        opnfv-releng-version: master
+        gs-pathname: '/{stream}'
+#--------------------------------
+# Scenarios
+#--------------------------------
+    scenario:
+        - 'os-nosdn-nofeature-ha':
+            auto-trigger-name: 'daily-trigger-disabled'
+            xci-flavor: 'ha'
+        - 'os-nosdn-nofeature-noha':
+            auto-trigger-name: 'daily-trigger-disabled'
+            xci-flavor: 'noha'
+#--------------------------------
+# XCI PODs
+#--------------------------------
+    pod:
+        - virtual:
+            <<: *master
+        - virtual:
+            <<: *ocata
+#--------------------------------
+# Supported Distros
+#--------------------------------
+    distro:
+        - 'xenial':
+            disabled: false
+            slave-label: xci-xenial-virtual
+            dib-os-release: 'xenial'
+            dib-os-element: 'ubuntu-minimal'
+            dib-os-packages: 'vlan,vim,less,bridge-utils,sudo,language-pack-en,iputils-ping,rsyslog,curl,python,debootstrap,ifenslave,ifenslave-2.6,lsof,lvm2,tcpdump,nfs-kernel-server,chrony,iptabls'
+            extra-dib-elements: 'openssh-server'
+        - 'centos7':
+            disabled: true
+            slave-label: xci-centos7-virtual
+            dib-os-release: '7'
+            dib-os-element: 'centos7'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
+        - 'suse':
+            disabled: true
+            slave-label: xci-suse-virtual
+            dib-os-release: '42.2'
+            dib-os-element: 'opensuse-minimal'
+            dib-os-packages: 'vim,less,bridge-utils,iputils,rsyslog,curl'
+            extra-dib-elements: 'openssh-server'
+
+#--------------------------------
+#        Phases
+#--------------------------------
+    phase:
+        - 'deploy'
+        - 'functest'
+#--------------------------------
+# jobs
+#--------------------------------
+    jobs:
+        - 'xci-{scenario}-{pod}-{distro}-daily-{stream}'
+        - 'xci-{phase}-{pod}-{distro}-daily-{stream}'
+
+#--------------------------------
+# job templates
+#--------------------------------
+- job-template:
+    name: 'xci-{scenario}-{pod}-{distro}-daily-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - '^xci-os.*'
+                - '^xci-deploy.*'
+                - '^xci-functest.*'
+                - '^bifrost-.*periodic.*'
+                - '^osa-.*periodic.*'
+            block-level: 'NODE'
+        - logrotate-default
+
+    parameters:
+        - string:
+            name: DEPLOY_SCENARIO
+            default: '{scenario}'
+        - string:
+            name: XCI_FLAVOR
+            default: '{xci-flavor}'
+        - label:
+            name: SLAVE_LABEL
+            default: '{slave-label}'
+        - string:
+            name: XCI_LOOP
+            default: 'daily'
+
+    triggers:
+        - '{auto-trigger-name}'
+
+    wrappers:
+        - fix-workspace-permissions
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME"
+        - trigger-builds:
+            - project: 'xci-deploy-{pod}-{distro}-daily-{stream}'
+              current-parameters: false
+              predefined-parameters: |
+                DEPLOY_SCENARIO=$DEPLOY_SCENARIO
+                XCI_FLAVOR=$XCI_FLAVOR
+                XCI_LOOP=$XCI_LOOP
+              same-node: true
+              block: true
+        - trigger-builds:
+            - project: 'xci-functest-{pod}-{distro}-daily-{stream}'
+              current-parameters: false
+              predefined-parameters: |
+                DEPLOY_SCENARIO=$DEPLOY_SCENARIO
+                XCI_FLAVOR=$XCI_FLAVOR
+                XCI_LOOP=$XCI_LOOP
+              same-node: true
+              block: true
+              block-thresholds:
+                build-step-failure-threshold: 'never'
+                failure-threshold: 'never'
+                unstable-threshold: 'FAILURE'
+
+    publishers:
+        - email:
+            recipients: fatih.degirmenci@ericsson.com yroblamo@redhat.com mchandras@suse.de jack.morgan@intel.com julienjut@gmail.com
+
+- job-template:
+    name: 'xci-{phase}-{pod}-{distro}-daily-{stream}'
+
+    disabled: '{obj:disabled}'
+
+    concurrent: false
+
+    properties:
+        - build-blocker:
+            use-build-blocker: true
+            blocking-jobs:
+                - '^xci-deploy.*'
+                - '^xci-functest.*'
+                - '^bifrost-.*periodic.*'
+                - '^osa-.*periodic.*'
+            block-level: 'NODE'
+        - logrotate-default
+
+    wrappers:
+        - fix-workspace-permissions
+
+    scm:
+        - git-scm
+
+    parameters:
+        - project-parameter:
+            project: '{project}'
+            branch: '{opnfv-releng-version}'
+        - string:
+            name: GIT_BASE
+            default: https://gerrit.opnfv.org/gerrit/$PROJECT
+        - string:
+            name: DEPLOY_SCENARIO
+            default: 'os-nosdn-nofeature-ha'
+        - string:
+            name: XCI_FLAVOR
+            default: 'ha'
+        - string:
+            name: DISTRO
+            default: '{distro}'
+        - string:
+            name: DIB_OS_RELEASE
+            default: '{dib-os-release}'
+        - string:
+            name: DIB_OS_ELEMENT
+            default: '{dib-os-element}'
+        - string:
+            name: DIB_OS_PACKAGES
+            default: '{dib-os-packages}'
+        - string:
+            name: EXTRA_DIB_ELEMENTS
+            default: '{extra-dib-elements}'
+        - string:
+            name: CLEAN_DIB_IMAGES
+            default: 'true'
+        - label:
+            name: SLAVE_LABEL
+            default: '{slave-label}'
+        - string:
+            name: ANSIBLE_VERBOSITY
+            default: ''
+        - string:
+            name: XCI_LOOP
+            default: 'daily'
+
+    builders:
+        - description-setter:
+            description: "Built on $NODE_NAME - Scenario: $DEPLOY_SCENARIO"
+        - 'xci-{phase}-builder'
+
+#---------------------------
+# builder macros
+#---------------------------
+- builder:
+    name: xci-deploy-builder
+    builders:
+        - shell:
+            !include-raw: ./xci-deploy.sh
+
+- builder:
+    name: xci-functest-builder
+    builders:
+        - shell:
+            !include-raw: ./xci-functest.sh
diff --git a/jjb/xci/xci-deploy.sh b/jjb/xci/xci-deploy.sh
new file mode 100755 (executable)
index 0000000..b007b85
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o nounset
+set -o pipefail
+
+cd $WORKSPACE/prototypes/xci
+
+# for daily jobs, we want to use working versions
+# for periodic jobs, we will use whatever is set in the job, probably master
+if [[ "$XCI_LOOP" == "daily" ]]; then
+    # source pinned-vars to get releng version
+    source ./config/pinned-versions
+
+    # checkout the version
+    git checkout -q $OPNFV_RELENG_VERSION
+    echo "Info: Using $OPNFV_RELENG_VERSION"
+elif [[ "$XCI_LOOP" == "periodic" ]]; then
+    echo "Info: Using $OPNFV_RELENG_VERSION"
+fi
+
+# this is just an example to give the idea about what we need to do
+# so ignore this part for the timebeing as we need to adjust xci-deploy.sh
+# to take this into account while deploying anyways
+# clone openstack-ansible
+# stable/ocata already use pinned versions so this is only valid for master
+if [[ "$XCI_LOOP" == "periodic" && "$OPENSTACK_OSA_VERSION" == "master" ]]; then
+    cd $WORKSPACE
+    # get the url to openstack-ansible git
+    source ./config/env-vars
+    echo "Info: Capture the ansible role requirement versions before doing anything"
+    git clone -q $OPENSTACK_OSA_GIT_URL
+    cd openstack-ansible
+    cat ansible-role-requirements.yml | while IFS= read -r line
+    do
+        if [[ $line =~ "src:" ]]; then
+            repo_url=$(echo $line | awk {'print $2'})
+            repo_sha1=$(git ls-remote $repo_url $OPENSTACK_OSA_VERSION | awk {'print $1'})
+        fi
+        echo "$line" | sed -e "s|master|$repo_sha1|" >> opnfv-ansible-role-requirements.yml
+    done
+    echo "Info: SHA1s of ansible role requirements"
+    echo "-------------------------------------------------------------------------"
+    cat opnfv-ansible-role-requirements.yml
+    echo "-------------------------------------------------------------------------"
+fi
+
+# proceed with the deployment
+cd $WORKSPACE/prototypes/xci
+sudo -E ./xci-deploy.sh
+
+if [[ "$JOB_NAME" =~ "periodic" && "$OPENSTACK_OSA_VERSION" == "master" ]]; then
+    # if we arrived here without failing, it means we have something we can pin
+    # this is again here to show the intention
+    cd $WORKSPACE/openstack-ansible
+    OSA_GIT_SHA1=$(git rev-parse HEAD)
+
+    # log some info
+    echo -e "\n"
+    echo "***********************************************************************"
+    echo "*                          OSA SHA1 TO PIN                            *"
+    echo "*                                                                     *"
+    echo "    $OSA_GIT_SHA1"
+    echo "*                                                                     *"
+    echo "***********************************************************************"
+fi
+
+echo -e "\n"
diff --git a/jjb/xci/xci-functest.sh b/jjb/xci/xci-functest.sh
new file mode 100755 (executable)
index 0000000..0f58dfe
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Functional testing with functest"
index 9d80e42..1f2f312 100644 (file)
@@ -14,8 +14,8 @@
         branch: '{stream}'
         gs-pathname: ''
         docker-tag: 'latest'
-    colorado: &colorado
-        stream: colorado
+    danube: &danube
+        stream: danube
         branch: 'stable/{stream}'
         gs-pathname: '{stream}'
         docker-tag: 'stable'
             slave-label: fuel-baremetal
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: fuel-virtual
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 # armband CI PODs
         - armband-baremetal:
             slave-label: armband-baremetal
             slave-label: armband-baremetal
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - armband-virtual:
             slave-label: armband-virtual
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 # joid CI PODs
         - baremetal:
             slave-label: joid-baremetal
             slave-label: joid-baremetal
             installer: joid
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: joid-virtual
             installer: joid
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 
 # compass CI PODs
         - baremetal:
             slave-label: compass-baremetal
             installer: compass
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - virtual:
             slave-label: compass-virtual
             installer: compass
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 #--------------------------------
 #    Installers not using labels
 #            CI PODs
             slave-label: '{pod}'
             installer: apex
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
 #--------------------------------
 #        None-CI PODs
 #--------------------------------
             slave-label: '{pod}'
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - zte-pod2:
             slave-label: '{pod}'
             installer: fuel
             slave-label: '{pod}'
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - arm-pod2:
             slave-label: '{pod}'
             installer: fuel
             slave-label: '{pod}'
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - arm-pod3:
             slave-label: '{pod}'
             installer: fuel
             slave-label: '{pod}'
             installer: fuel
             auto-trigger-name: 'daily-trigger-disabled'
-            <<: *colorado
+            <<: *danube
         - orange-pod2:
             slave-label: '{pod}'
             installer: joid
             installer: compass
             auto-trigger-name: 'yardstick-daily-huawei-pod4-trigger'
             <<: *master
-        - huawei-pod5:
-            slave-label: '{pod}'
+        - baremetal-centos:
+            slave-label: 'intel-pod8'
             installer: compass
             auto-trigger-name: 'daily-trigger-disabled'
             <<: *master
     concurrent: true
 
     properties:
+        - logrotate-default
         - throttle:
             enabled: true
             max-per-node: 1
     parameters:
         - project-parameter:
             project: '{project}'
+            branch: '{branch}'
         - '{installer}-defaults'
         - '{slave-label}-defaults'
         - 'yardstick-params-{slave-label}'
             description: "Show debut output information"
 
     scm:
-        - git-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            branch: '{branch}'
+        - git-scm
 
     builders:
         - description-setter:
         - 'yardstick-cleanup'
         #- 'yardstick-fetch-os-creds'
         - 'yardstick-{testsuite}'
+        - 'yardstick-store-results'
 
     publishers:
         - email:
-            recipients: jean.gaoliang@huawei.com matthew.lijun@huawei.com
+            recipients: jean.gaoliang@huawei.com limingjiang@huawei.com
 
 ########################
 # builder macros
         - shell:
             !include-raw: ../../utils/fetch_os_creds.sh
 
+- builder:
+    name: yardstick-store-results
+    builders:
+        - shell:
+            !include-raw: ../../utils/push-test-logs.sh
+
 - builder:
     name: yardstick-cleanup
     builders:
             name: YARDSTICK_DB_BACKEND
             default: '-i 104.197.68.199:8086'
             description: 'Arguments to use in order to choose the backend DB'
-
-- parameter:
-    name: 'yardstick-params-huawei-pod5'
-    parameters:
-        - string:
-            name: YARDSTICK_DB_BACKEND
-            default: '-i 104.197.68.199:8086'
-            description: 'Arguments to use in order to choose the backend DB'
-
 - parameter:
     name: 'yardstick-params-zte-pod1'
     parameters:
index 4e6f7d6..51455b5 100755 (executable)
@@ -1,6 +1,20 @@
 #!/bin/bash
 [[ $CI_DEBUG == true ]] && redirect="/dev/stdout" || redirect="/dev/null"
 
+# Remove containers along with image opnfv/yardstick*:<none>
+dangling_images=($(docker images -f "dangling=true" | grep opnfv/yardstick | awk '{print $3}'))
+if [[ -n ${dangling_images} ]]; then
+    echo "Removing opnfv/yardstick:<none> images and their containers..."
+    for image_id in "${dangling_images[@]}"; do
+        echo "      Removing image_id: $image_id and its containers"
+        containers=$(docker ps -a | grep $image_id | awk '{print $1}')
+        if [[ -n "$containers" ]];then
+            docker rm -f $containers >${redirect}
+        fi
+        docker rmi $image_id >${redirect}
+    done
+fi
+
 echo "Cleaning up docker containers/images..."
 # Remove previous running containers if exist
 if [[ ! -z $(docker ps -a | grep opnfv/yardstick) ]]; then
@@ -17,6 +31,6 @@ if [[ ! -z $(docker images | grep opnfv/yardstick) ]]; then
     for tag in "${image_tags[@]}"; do
         echo "Removing docker image opnfv/yardstick:$tag..."
         docker rmi opnfv/yardstick:$tag >$redirect
-
     done
 fi
+
index b370541..973f83a 100755 (executable)
@@ -18,7 +18,7 @@ if [[ ${INSTALLER_TYPE} == 'apex' ]]; then
 elif [[ ${INSTALLER_TYPE} == 'joid' ]]; then
     # If production lab then creds may be retrieved dynamically
     # creds are on the jumphost, always in the same folder
-    labconfig="-v $LAB_CONFIG/admin-openrc:/home/opnfv/openrc"
+    labconfig="-v $LAB_CONFIG/admin-openrc:/etc/yardstick/openstack.creds"
     # If dev lab, credentials may not be the default ones, just provide a path to put them into docker
     # replace the default one by the customized one provided by jenkins config
 fi
@@ -31,14 +31,21 @@ fi
 opts="--privileged=true --rm"
 envs="-e INSTALLER_TYPE=${INSTALLER_TYPE} -e INSTALLER_IP=${INSTALLER_IP} \
     -e NODE_NAME=${NODE_NAME} -e EXTERNAL_NETWORK=${EXTERNAL_NETWORK} \
-    -e YARDSTICK_BRANCH=${GIT_BRANCH##origin/} -e DEPLOY_SCENARIO=${DEPLOY_SCENARIO}"
+    -e YARDSTICK_BRANCH=${BRANCH} -e DEPLOY_SCENARIO=${DEPLOY_SCENARIO}"
 
 # Pull the image with correct tag
 echo "Yardstick: Pulling image opnfv/yardstick:${DOCKER_TAG}"
 docker pull opnfv/yardstick:$DOCKER_TAG >$redirect
 
+# map log directory
+branch=${BRANCH##*/}
+dir_result="${HOME}/opnfv/yardstick/results/${branch}"
+mkdir -p ${dir_result}
+sudo rm -rf ${dir_result}/*
+map_log_dir="-v ${dir_result}:/tmp/yardstick"
+
 # Run docker
-cmd="sudo docker run ${opts} ${envs} ${labconfig} ${sshkey} opnfv/yardstick:${DOCKER_TAG} \
+cmd="sudo docker run ${opts} ${envs} ${labconfig} ${map_log_dir} ${sshkey} opnfv/yardstick:${DOCKER_TAG} \
     exec_tests.sh ${YARDSTICK_DB_BACKEND} ${YARDSTICK_SCENARIO_SUITE_NAME}"
 echo "Yardstick: Running docker cmd: ${cmd}"
 ${cmd}
index db07e9d..bbfa152 100644 (file)
@@ -16,7 +16,7 @@
             branch: '{stream}'
             gs-pathname: ''
             disabled: false
-        - colorado:
+        - danube:
             branch: 'stable/{stream}'
             gs-pathname: '/{stream}'
             disabled: false
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: '$GERRIT_REFSPEC'
-            choosing-strategy: 'gerrit'
+        - git-scm-gerrit
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - patchset-created-event:
                     exclude-drafts: 'false'
@@ -72,7 +69,6 @@
     parameters:
         - project-parameter:
             project: '{project}'
-        - gerrit-parameter:
             branch: '{branch}'
         - 'opnfv-build-ubuntu-defaults'
         - string:
             description: "Directory where the build artifact will be located upon the completion     of the build."
 
     scm:
-        - gerrit-trigger-scm:
-            credentials-id: '{ssh-credentials}'
-            refspec: ''
-            choosing-strategy: 'default'
+        - git-scm
 
     triggers:
         - gerrit:
+            server-name: 'gerrit.opnfv.org'
             trigger-on:
                 - change-merged-event
                 - comment-added-contains-event:
             set -o errexit
             set -o pipefail
 
+            sudo apt-get install -y build-essential python-dev python3-dev
+
             echo "Running unit tests..."
             cd $WORKSPACE
-            virtualenv $WORKSPACE/yardstick_venv
-            source $WORKSPACE/yardstick_venv/bin/activate
-
-            # install python packages
-            easy_install -U setuptools
-            easy_install -U pip
-            pip install -r tests/ci/requirements.txt
-            pip install -e .
-
-            # unit tests
-            ./run_tests.sh
-
-            deactivate
+            tox
diff --git a/modules/README.rst b/modules/README.rst
new file mode 100644 (file)
index 0000000..caec46b
--- /dev/null
@@ -0,0 +1,13 @@
+
+This directory may be used to add new tools that might be useful for any
+project in OPNFV. This tools must be python based and shall be imported
+as follows:
+
+  from opnfv.utils import SSHUtils
+  from opnfv.utils import OPNFVLogger
+  from opnfv.utils import OPNFVException
+  from opnfv.utils import constants
+
+For further information about how to use this modules directory, contact:
+  fatih.degirmenci@ericsson.com
+  jose.lausuch@ericsson.com
diff --git a/modules/opnfv/deployment/__init__.py b/modules/opnfv/deployment/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/apex/__init__.py b/modules/opnfv/deployment/apex/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/apex/adapter.py b/modules/opnfv/deployment/apex/adapter.py
new file mode 100644 (file)
index 0000000..225e174
--- /dev/null
@@ -0,0 +1,100 @@
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# Author: Jose Lausuch (jose.lausuch@ericsson.com)
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import re
+
+from opnfv.deployment import manager
+from opnfv.utils import opnfv_logger as logger
+from opnfv.utils import ssh_utils
+
+logger = logger.Logger(__name__).getLogger()
+
+
+class ApexAdapter(manager.DeploymentHandler):
+
+    def __init__(self, installer_ip, installer_user, pkey_file):
+        super(ApexAdapter, self).__init__(installer='apex',
+                                          installer_ip=installer_ip,
+                                          installer_user=installer_user,
+                                          installer_pwd=None,
+                                          pkey_file=pkey_file)
+
+    def get_nodes(self):
+        nodes = []
+        cmd = "source /home/stack/stackrc;openstack server list"
+        output = self.installer_node.run_cmd(cmd)
+        lines = output.rsplit('\n')
+        if len(lines) < 4:
+            logger.info("No nodes found in the deployment.")
+            return None
+
+        for line in lines:
+            roles = []
+            if any(x in line for x in ['-----', 'Networks']):
+                continue
+            if 'controller' in line:
+                roles.append(manager.Role.CONTROLLER)
+            if 'compute' in line:
+                roles.append(manager.Role.COMPUTE)
+            if 'opendaylight' in line.lower():
+                roles.append(manager.Role.ODL)
+
+            fields = line.split('|')
+            id = re.sub('[!| ]', '', fields[1]).encode()
+            name = re.sub('[!| ]', '', fields[2]).encode()
+            status_node = re.sub('[!| ]', '', fields[3]).encode().lower()
+            ip = re.sub('[!| ctlplane=]', '', fields[4]).encode()
+
+            ssh_client = None
+            if 'active' in status_node:
+                status = manager.NodeStatus.STATUS_OK
+                ssh_client = ssh_utils.get_ssh_client(hostname=ip,
+                                                      username='heat-admin',
+                                                      pkey_file=self.pkey_file)
+            elif 'error' in status_node:
+                status = manager.NodeStatus.STATUS_ERROR
+            elif 'off' in status_node:
+                status = manager.NodeStatus.STATUS_OFFLINE
+            else:
+                status = manager.NodeStatus.STATUS_INACTIVE
+
+            node = manager.Node(id, ip, name, status, roles, ssh_client)
+            nodes.append(node)
+
+        return nodes
+
+    def get_openstack_version(self):
+        cmd = 'source overcloudrc;sudo nova-manage version'
+        result = self.installer_node.run_cmd(cmd)
+        return result
+
+    def get_sdn_version(self):
+        cmd_descr = ("sudo yum info opendaylight 2>/dev/null|"
+                     "grep Description|sed 's/^.*\: //'")
+        cmd_ver = ("sudo yum info opendaylight 2>/dev/null|"
+                   "grep Version|sed 's/^.*\: //'")
+        description = None
+        for node in self.nodes:
+            if node.is_controller():
+                description = node.run_cmd(cmd_descr)
+                version = node.run_cmd(cmd_ver)
+                break
+
+        if description is None:
+            return None
+        else:
+            return description + ':' + version
+
+    def get_deployment_status(self):
+        cmd = 'source stackrc;openstack stack list|grep CREATE_COMPLETE'
+        result = self.installer_node.run_cmd(cmd)
+        if result is None or len(result) == 0:
+            return 'failed'
+        else:
+            return 'active'
diff --git a/modules/opnfv/deployment/compass/__init__.py b/modules/opnfv/deployment/compass/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/compass/adapter.py b/modules/opnfv/deployment/compass/adapter.py
new file mode 100644 (file)
index 0000000..38aa452
--- /dev/null
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2017 HUAWEI TECHNOLOGIES CO.,LTD and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import json
+import netaddr
+import re
+
+from opnfv.deployment import manager
+from opnfv.utils import opnfv_logger as logger
+from opnfv.utils import ssh_utils
+
+logger = logger.Logger(__name__).getLogger()
+
+
+class CompassAdapter(manager.DeploymentHandler):
+
+    def __init__(self, installer_ip, installer_user, installer_pwd):
+        super(CompassAdapter, self).__init__(installer='compass',
+                                             installer_ip=installer_ip,
+                                             installer_user=installer_user,
+                                             installer_pwd=installer_pwd,
+                                             pkey_file=None)
+
+    def get_nodes(self, options=None):
+        nodes = []
+        self.deployment_status = None
+        self.nodes_dict = self._get_deployment_nodes()
+        self.deployment_status = self.get_deployment_status()
+
+        for k, v in self.nodes_dict.iteritems():
+            node = manager.Node(v['id'], v['ip'],
+                                k, v['status'],
+                                v['roles'], v['ssh_client'], v['mac'])
+            nodes.append(node)
+
+        self.get_nodes_called = True
+        return nodes
+
+    def get_openstack_version(self):
+        version = None
+        cmd = 'source /opt/admin-openrc.sh;nova-manage version 2>/dev/null'
+        version = next(node.run_cmd(cmd) for node in self.nodes
+                       if node.is_controller())
+        return version
+
+    def get_sdn_version(self):
+        for node in self.nodes:
+            if node.is_odl():
+                sdn_info = self._get_sdn_info(node, manager.Role.ODL)
+                break
+            elif node.is_onos():
+                sdn_info = self._get_sdn_info(node, manager.Role.ONOS)
+                break
+            else:
+                sdn_info = None
+        return sdn_info
+
+    def _get_sdn_info(self, node, sdn_type):
+        if sdn_type == manager.Role.ODL:
+            sdn_key = 'distribution-karaf'
+        elif sdn_type == manager.Role.ONOS:
+            sdn_key = 'onos-'
+        else:
+            raise KeyError('SDN %s is not supported', sdn_type)
+
+        cmd = "find /opt -name '{0}*'".format(sdn_key)
+        sdn_info = node.run_cmd(cmd)
+        sdn_version = 'None'
+        if sdn_info:
+            # /opt/distribution-karaf-0.5.2-Boron-SR2.tar.gz
+            match_sdn = re.findall(r".*(0\.\d\.\d).*", sdn_info)
+            if (match_sdn and len(match_sdn) >= 1):
+                sdn_version = match_sdn[0]
+                sdn_version = '{0} {1}'.format(sdn_type, sdn_version)
+        return sdn_version
+
+    def get_deployment_status(self):
+        if self.deployment_status is not None:
+            logger.debug('Skip - Node status has been retrieved once')
+            return self.deployment_status
+
+        for k, v in self.nodes_dict.iteritems():
+            if manager.Role.CONTROLLER in v['roles']:
+                cmd = 'source /opt/admin-openrc.sh; nova hypervisor-list;'
+                '''
+                +----+---------------------+-------+---------+
+
+                | ID | Hypervisor hostname | State | Status  |
+
+                +----+---------------------+-------+---------+
+
+                | 3  | host4               | up    | enabled |
+
+                | 6  | host5               | up    | enabled |
+
+                +----+---------------------+-------+---------+
+                '''
+                _, stdout, stderr = (v['ssh_client'].exec_command(cmd))
+                error = stderr.readlines()
+                if len(error) > 0:
+                    logger.error("error %s" % ''.join(error))
+                    status = manager.NodeStatus.STATUS_ERROR
+                    v['status'] = status
+                    continue
+
+                lines = stdout.readlines()
+                for i in range(3, len(lines) - 1):
+                    fields = lines[i].strip().encode().rsplit(' | ')
+                    hostname = fields[1].strip().encode().lower()
+                    state = fields[2].strip().encode().lower()
+                    if 'up' == state:
+                        status = manager.NodeStatus.STATUS_OK
+                    else:
+                        status = manager.NodeStatus.STATUS_ERROR
+                    self.nodes_dict[hostname]['status'] = status
+                    v['status'] = manager.NodeStatus.STATUS_OK
+
+        failed_nodes = [k for k, v in self.nodes_dict.iteritems()
+                        if v['status'] != manager.NodeStatus.STATUS_OK]
+
+        if failed_nodes and len(failed_nodes) > 0:
+            return 'Hosts {0} failed'.format(','.join(failed_nodes))
+
+        return 'active'
+
+    def _get_deployment_nodes(self):
+        sql_query = ('select host.host_id, host.roles, '
+                     'network.ip_int, machine.mac from clusterhost as host, '
+                     'host_network as network, machine as machine '
+                     'where host.host_id=network.host_id '
+                     'and host.id=machine.id;')
+        cmd = 'mysql -uroot -Dcompass -e "{0}"'.format(sql_query)
+        logger.debug('mysql command: %s', cmd)
+        output = self.installer_node.run_cmd(cmd)
+        '''
+        host_id roles   ip_int  mac
+        1 ["controller", "ha", "odl", "ceph-adm", "ceph-mon"]
+        167837746 00:00:e3:ee:a8:63
+        2 ["controller", "ha", "odl", "ceph-mon"]
+        167837747 00:00:31:1d:16:7a
+        3 ["controller", "ha", "odl", "ceph-mon"]
+        167837748 00:00:0c:bf:eb:01
+        4 ["compute", "ceph-osd"] 167837749 00:00:d8:22:6f:59
+        5 ["compute", "ceph-osd"] 167837750 00:00:75:d5:6b:9e
+        '''
+        lines = output.encode().rsplit('\n')
+        nodes_dict = {}
+        if (not lines or len(lines) < 2):
+            logger.error('No nodes are found in the deployment.')
+            return nodes_dict
+
+        proxy = {'ip': self.installer_ip,
+                 'username': self.installer_user,
+                 'password': self.installer_pwd}
+        for i in range(1, len(lines)):
+            fields = lines[i].strip().encode().rsplit('\t')
+            host_id = fields[0].strip().encode()
+            name = 'host{0}'.format(host_id)
+            node_roles_str = fields[1].strip().encode().lower()
+            node_roles_list = json.loads(node_roles_str)
+            node_roles = [manager.Role.ODL if x == 'odl'
+                          else x for x in node_roles_list]
+            roles = [x for x in [manager.Role.CONTROLLER,
+                                 manager.Role.COMPUTE,
+                                 manager.Role.ODL,
+                                 manager.Role.ONOS] if x in node_roles]
+            ip = fields[2].strip().encode()
+            ip = str(netaddr.IPAddress(ip))
+            mac = fields[3].strip().encode()
+
+            nodes_dict[name] = {}
+            nodes_dict[name]['id'] = host_id
+            nodes_dict[name]['roles'] = roles
+            nodes_dict[name]['ip'] = ip
+            nodes_dict[name]['mac'] = mac
+            ssh_client = ssh_utils.get_ssh_client(hostname=ip,
+                                                  username='root',
+                                                  proxy=proxy)
+            nodes_dict[name]['ssh_client'] = ssh_client
+            nodes_dict[name]['status'] = manager.NodeStatus.STATUS_UNKNOWN
+        return nodes_dict
diff --git a/modules/opnfv/deployment/example.py b/modules/opnfv/deployment/example.py
new file mode 100644 (file)
index 0000000..52d9b56
--- /dev/null
@@ -0,0 +1,50 @@
+# This is an example of usage of this Tool
+# Author: Jose Lausuch (jose.lausuch@ericsson.com)
+
+from opnfv.deployment import factory
+
+print("########## APEX ##########")
+handler = factory.Factory.get_handler('apex',
+                                      '192.168.122.135',
+                                      'stack',
+                                      pkey_file='/root/.ssh/id_rsa')
+
+
+installer_node = handler.get_installer_node()
+print("Hello, I am node '%s'" % installer_node.run_cmd('hostname'))
+installer_node.get_file('/home/stack/overcloudrc', './overcloudrc')
+
+nodes = handler.get_nodes()
+for node in nodes:
+    print("Hello, I am node '%s' and my ip is %s." %
+          (node.run_cmd('hostname'), node.ip))
+
+print(handler.get_deployment_info())
+
+
+print("########## FUEL ##########")
+handler = factory.Factory.get_handler('fuel',
+                                      '10.20.0.2',
+                                      'root',
+                                      installer_pwd='r00tme')
+
+print(handler.get_deployment_info())
+
+print("List of nodes in cluster 4:")
+nodes = handler.get_nodes({'cluster': '4'})
+for node in nodes:
+    print(node)
+
+
+print("########## COMPASS ##########")
+handler = factory.Factory.get_handler('compass',
+                                      '192.168.200.2',
+                                      'root',
+                                      installer_pwd='root')
+
+print(handler.get_deployment_status())
+print(handler.get_deployment_info())
+print('Details of each node:')
+nodes = handler.nodes
+for node in nodes:
+    print(node)
diff --git a/modules/opnfv/deployment/factory.py b/modules/opnfv/deployment/factory.py
new file mode 100644 (file)
index 0000000..b8e5c8e
--- /dev/null
@@ -0,0 +1,51 @@
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# Author: Jose Lausuch (jose.lausuch@ericsson.com)
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from opnfv.deployment.apex import adapter as apex_adapter
+from opnfv.deployment.compass import adapter as compass_adapter
+from opnfv.deployment.fuel import adapter as fuel_adapter
+from opnfv.utils import opnfv_logger as logger
+
+logger = logger.Logger(__name__).getLogger()
+
+
+class Factory(object):
+
+    INSTALLERS = ["fuel", "apex", "compass", "joid", "daisy"]
+
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def get_handler(installer,
+                    installer_ip,
+                    installer_user,
+                    installer_pwd=None,
+                    pkey_file=None):
+
+        if installer not in Factory.INSTALLERS:
+            raise Exception("This is not an OPNFV installer.")
+
+        if installer.lower() == "apex":
+            return apex_adapter.ApexAdapter(installer_ip=installer_ip,
+                                            installer_user=installer_user,
+                                            pkey_file=pkey_file)
+        elif installer.lower() == "fuel":
+            return fuel_adapter.FuelAdapter(installer_ip=installer_ip,
+                                            installer_user=installer_user,
+                                            installer_pwd=installer_pwd)
+        elif installer.lower() == "compass":
+            return compass_adapter.CompassAdapter(
+                installer_ip=installer_ip,
+                installer_user=installer_user,
+                installer_pwd=installer_pwd)
+        else:
+            raise Exception("Installer adapter is not implemented for "
+                            "the given installer.")
diff --git a/modules/opnfv/deployment/fuel/__init__.py b/modules/opnfv/deployment/fuel/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/fuel/adapter.py b/modules/opnfv/deployment/fuel/adapter.py
new file mode 100644 (file)
index 0000000..a217767
--- /dev/null
@@ -0,0 +1,199 @@
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# Author: Jose Lausuch (jose.lausuch@ericsson.com)
+#         George Paraskevopoulos (geopar@intracom-telecom.com)
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from opnfv.deployment import manager
+from opnfv.utils import opnfv_logger as logger
+from opnfv.utils import ssh_utils
+
+logger = logger.Logger(__name__).getLogger()
+
+
+class FuelAdapter(manager.DeploymentHandler):
+
+    def __init__(self, installer_ip, installer_user, installer_pwd):
+        super(FuelAdapter, self).__init__(installer='fuel',
+                                          installer_ip=installer_ip,
+                                          installer_user=installer_user,
+                                          installer_pwd=installer_pwd,
+                                          pkey_file=None)
+
+    def _get_clusters(self):
+        environments = []
+        output = self.runcmd_fuel_env()
+        lines = output.rsplit('\n')
+        if len(lines) < 2:
+            logger.info("No environments found in the deployment.")
+            return None
+        else:
+            fields = lines[0].rsplit(' | ')
+
+            index_id = -1
+            index_status = -1
+            index_name = -1
+            index_release_id = -1
+
+            for i in range(len(fields)):
+                if "id" in fields[i]:
+                    index_id = i
+                elif "status" in fields[i]:
+                    index_status = i
+                elif "name" in fields[i]:
+                    index_name = i
+                elif "release_id" in fields[i]:
+                    index_release_id = i
+
+            # order env info
+            for i in range(2, len(lines)):
+                fields = lines[i].rsplit(' | ')
+                dict = {"id": fields[index_id].strip(),
+                        "status": fields[index_status].strip(),
+                        "name": fields[index_name].strip(),
+                        "release_id": fields[index_release_id].strip()}
+                environments.append(dict)
+
+        return environments
+
+    def get_nodes(self, options=None):
+
+        if options and options['cluster'] and len(self.nodes) > 0:
+            n = []
+            for node in self.nodes:
+                if str(node.info['cluster']) == str(options['cluster']):
+                    n.append(node)
+            return n
+
+        try:
+            # if we have retrieved previously all the nodes, don't do it again
+            # This fails the first time when the constructor calls this method
+            # therefore the try/except
+            if len(self.nodes) > 0:
+                return self.nodes
+        except:
+            pass
+
+        nodes = []
+        cmd = 'fuel node'
+        output = self.installer_node.run_cmd(cmd)
+        lines = output.rsplit('\n')
+        if len(lines) < 2:
+            logger.info("No nodes found in the deployment.")
+            return nodes
+
+        # get fields indexes
+        fields = lines[0].rsplit(' | ')
+
+        index_id = -1
+        index_status = -1
+        index_name = -1
+        index_cluster = -1
+        index_ip = -1
+        index_mac = -1
+        index_roles = -1
+        index_online = -1
+
+        for i in range(len(fields)):
+            if "group_id" in fields[i]:
+                break
+            elif "id" in fields[i]:
+                index_id = i
+            elif "status" in fields[i]:
+                index_status = i
+            elif "name" in fields[i]:
+                index_name = i
+            elif "cluster" in fields[i]:
+                index_cluster = i
+            elif "ip" in fields[i]:
+                index_ip = i
+            elif "mac" in fields[i]:
+                index_mac = i
+            elif "roles " in fields[i] and "pending_roles" not in fields[i]:
+                index_roles = i
+            elif "online" in fields[i]:
+                index_online = i
+
+        # order nodes info
+        for i in range(2, len(lines)):
+            fields = lines[i].rsplit(' | ')
+            id = fields[index_id].strip().encode()
+            ip = fields[index_ip].strip().encode()
+            status_node = fields[index_status].strip().encode().lower()
+            name = fields[index_name].strip().encode()
+            roles_all = fields[index_roles].strip().encode().lower()
+
+            roles = [x for x in [manager.Role.CONTROLLER,
+                                 manager.Role.COMPUTE,
+                                 manager.Role.ODL] if x in roles_all]
+
+            dict = {"cluster": fields[index_cluster].strip().encode(),
+                    "mac": fields[index_mac].strip().encode(),
+                    "status_node": status_node,
+                    "online": fields[index_online].strip().encode()}
+
+            ssh_client = None
+            if status_node == 'ready':
+                status = manager.NodeStatus.STATUS_OK
+                proxy = {'ip': self.installer_ip,
+                         'username': self.installer_user,
+                         'password': self.installer_pwd}
+                ssh_client = ssh_utils.get_ssh_client(hostname=ip,
+                                                      username='root',
+                                                      proxy=proxy)
+            elif 'error' in status_node:
+                status = manager.NodeStatus.STATUS_ERROR
+            elif 'off' in status_node:
+                status = manager.NodeStatus.STATUS_OFFLINE
+            elif 'discover' in status_node:
+                status = manager.NodeStatus.STATUS_UNUSED
+            else:
+                status = manager.NodeStatus.STATUS_INACTIVE
+
+            node = manager.Node(
+                id, ip, name, status, roles, ssh_client, dict)
+            if options and options['cluster']:
+                if fields[index_cluster].strip() == options['cluster']:
+                    nodes.append(node)
+            else:
+                nodes.append(node)
+
+        self.get_nodes_called = True
+        return nodes
+
+    def get_openstack_version(self):
+        cmd = 'source openrc;nova-manage version 2>/dev/null'
+        version = None
+        for node in self.nodes:
+            if node.is_controller() and node.is_active():
+                version = node.run_cmd(cmd)
+                break
+        return version
+
+    def get_sdn_version(self):
+        cmd = "apt-cache policy opendaylight|grep Installed"
+        version = None
+        for node in self.nodes:
+            if manager.Role.ODL in node.roles and node.is_active():
+                odl_version = node.run_cmd(cmd)
+                if odl_version:
+                    version = 'OpenDaylight ' + odl_version.split(' ')[-1]
+                    break
+        return version
+
+    def get_deployment_status(self):
+        cmd = "fuel env|tail -1|awk '{print $3}'"
+        result = self.installer_node.run_cmd(cmd)
+        if result is None or len(result) == 0:
+            return 'unknown'
+        elif 'operational' in result:
+            return 'active'
+        elif 'deploy' in result:
+            return 'deploying'
+        else:
+            return 'active'
diff --git a/modules/opnfv/deployment/manager.py b/modules/opnfv/deployment/manager.py
new file mode 100644 (file)
index 0000000..694df77
--- /dev/null
@@ -0,0 +1,393 @@
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# Author: Jose Lausuch (jose.lausuch@ericsson.com)
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+from abc import abstractmethod
+import os
+
+
+from opnfv.utils import opnfv_logger as logger
+from opnfv.utils import ssh_utils
+
+logger = logger.Logger(__name__).getLogger()
+
+
+class Deployment(object):
+
+    def __init__(self,
+                 installer,
+                 installer_ip,
+                 scenario,
+                 pod,
+                 status,
+                 openstack_version,
+                 sdn_controller,
+                 nodes=None):
+
+        self.deployment_info = {
+            'installer': installer,
+            'installer_ip': installer_ip,
+            'scenario': scenario,
+            'pod': pod,
+            'status': status,
+            'openstack_version': openstack_version,
+            'sdn_controller': sdn_controller,
+            'nodes': nodes
+        }
+
+    def _get_openstack_release(self):
+        '''
+        Translates an openstack version into the release name
+        '''
+        os_versions = {
+            '12': 'Liberty',
+            '13': 'Mitaka',
+            '14': 'Newton',
+            '15': 'Ocata',
+            '16': 'Pike',
+            '17': 'Queens'
+        }
+        try:
+            version = self.deployment_info['openstack_version'].split('.')[0]
+            name = os_versions[version]
+            return name
+        except Exception:
+            return 'Unknown release'
+
+    def get_dict(self):
+        '''
+        Returns a dictionary will all the attributes
+        '''
+        return self.deployment_info
+
+    def __str__(self):
+        '''
+        Override of the str method
+        '''
+        s = '''
+        INSTALLER:    {installer}
+        SCENARIO:     {scenario}
+        INSTALLER IP: {installer_ip}
+        POD:          {pod}
+        STATUS:       {status}
+        OPENSTACK:    {openstack_version} ({openstack_release})
+        SDN:          {sdn_controller}
+        NODES:
+    '''.format(installer=self.deployment_info['installer'],
+               scenario=self.deployment_info['scenario'],
+               installer_ip=self.deployment_info['installer_ip'],
+               pod=self.deployment_info['pod'],
+               status=self.deployment_info['status'],
+               openstack_version=self.deployment_info[
+            'openstack_version'],
+            openstack_release=self._get_openstack_release(),
+            sdn_controller=self.deployment_info['sdn_controller'])
+
+        for node in self.deployment_info['nodes']:
+            s += '{node_object}\n'.format(node_object=node)
+
+        return s
+
+
+class Role():
+    INSTALLER = 'installer'
+    CONTROLLER = 'controller'
+    COMPUTE = 'compute'
+    ODL = 'opendaylight'
+    ONOS = 'onos'
+
+
+class NodeStatus():
+    STATUS_OK = 'active'
+    STATUS_INACTIVE = 'inactive'
+    STATUS_OFFLINE = 'offline'
+    STATUS_ERROR = 'error'
+    STATUS_UNUSED = 'unused'
+    STATUS_UNKNOWN = 'unknown'
+
+
+class Node(object):
+
+    def __init__(self,
+                 id,
+                 ip,
+                 name,
+                 status,
+                 roles=None,
+                 ssh_client=None,
+                 info=None):
+        self.id = id
+        self.ip = ip
+        self.name = name
+        self.status = status
+        self.ssh_client = ssh_client
+        self.roles = roles
+        self.info = info
+
+        self.cpu_info = 'unknown'
+        self.memory = 'unknown'
+        self.ovs = 'unknown'
+
+        if ssh_client and Role.INSTALLER not in self.roles:
+            sys_info = self.get_system_info()
+            self.cpu_info = sys_info['cpu_info']
+            self.memory = sys_info['memory']
+            self.ovs = self.get_ovs_info()
+
+    def get_file(self, src, dest):
+        '''
+        SCP file from a node
+        '''
+        if self.status is not NodeStatus.STATUS_OK:
+            logger.info("The node %s is not active" % self.ip)
+            return 1
+        logger.info("Fetching %s from %s" % (src, self.ip))
+        get_file_result = ssh_utils.get_file(self.ssh_client, src, dest)
+        if get_file_result is None:
+            logger.error("SFTP failed to retrieve the file.")
+        else:
+            logger.info("Successfully copied %s:%s to %s" %
+                        (self.ip, src, dest))
+        return get_file_result
+
+    def put_file(self, src, dest):
+        '''
+        SCP file to a node
+        '''
+        if self.status is not NodeStatus.STATUS_OK:
+            logger.info("The node %s is not active" % self.ip)
+            return 1
+        logger.info("Copying %s to %s" % (src, self.ip))
+        put_file_result = ssh_utils.put_file(self.ssh_client, src, dest)
+        if put_file_result is None:
+            logger.error("SFTP failed to retrieve the file.")
+        else:
+            logger.info("Successfully copied %s to %s:%s" %
+                        (src, dest, self.ip))
+        return put_file_result
+
+    def run_cmd(self, cmd):
+        '''
+        Run command remotely on a node
+        '''
+        if self.status is not NodeStatus.STATUS_OK:
+            logger.error(
+                "Error running command %s. The node %s is not active"
+                % (cmd, self.ip))
+            return None
+        _, stdout, stderr = (self.ssh_client.exec_command(cmd))
+        error = stderr.readlines()
+        if len(error) > 0:
+            logger.error("error %s" % ''.join(error))
+            return None
+        output = ''.join(stdout.readlines()).rstrip()
+        return output
+
+    def get_dict(self):
+        '''
+        Returns a dictionary with all the attributes
+        '''
+        return {
+            'id': self.id,
+            'ip': self.ip,
+            'name': self.name,
+            'status': self.status,
+            'roles': self.roles,
+            'cpu_info': self.cpu_info,
+            'memory': self.memory,
+            'ovs': self.ovs,
+            'info': self.info
+        }
+
+    def is_active(self):
+        '''
+        Returns if the node is active
+        '''
+        if self.status == NodeStatus.STATUS_OK:
+            return True
+        return False
+
+    def is_controller(self):
+        '''
+        Returns if the node is a controller
+        '''
+        return Role.CONTROLLER in self.roles
+
+    def is_compute(self):
+        '''
+        Returns if the node is a compute
+        '''
+        return Role.COMPUTE in self.roles
+
+    def is_odl(self):
+        '''
+        Returns if the node is an opendaylight
+        '''
+        return Role.ODL in self.roles
+
+    def is_onos(self):
+        '''
+        Returns if the node is an ONOS
+        '''
+        return Role.ONOS in self.roles
+
+    def get_ovs_info(self):
+        '''
+        Returns the ovs version installed
+        '''
+        if self.is_active():
+            cmd = "ovs-vsctl --version|head -1| sed 's/^.*) //'"
+            return self.run_cmd(cmd)
+        return None
+
+    def get_system_info(self):
+        '''
+        Returns the ovs version installed
+        '''
+        cmd = 'grep MemTotal /proc/meminfo'
+        memory = self.run_cmd(cmd).partition('MemTotal:')[-1].strip().encode()
+
+        cpu_info = {}
+        cmd = 'lscpu'
+        result = self.run_cmd(cmd)
+        for line in result.splitlines():
+            if line.startswith('CPU(s)'):
+                cpu_info['num_cpus'] = line.split(' ')[-1].encode()
+            elif line.startswith('Thread(s) per core'):
+                cpu_info['threads/core'] = line.split(' ')[-1].encode()
+            elif line.startswith('Core(s) per socket'):
+                cpu_info['cores/socket'] = line.split(' ')[-1].encode()
+            elif line.startswith('Model name'):
+                cpu_info['model'] = line.partition(
+                    'Model name:')[-1].strip().encode()
+            elif line.startswith('Architecture'):
+                cpu_info['arch'] = line.split(' ')[-1].encode()
+
+        return {'memory': memory, 'cpu_info': cpu_info}
+
+    def __str__(self):
+        return '''
+            name:    {name}
+            id:      {id}
+            ip:      {ip}
+            status:  {status}
+            roles:   {roles}
+            cpu:     {cpu_info}
+            memory:  {memory}
+            ovs:     {ovs}
+            info:    {info}'''.format(name=self.name,
+                                      id=self.id,
+                                      ip=self.ip,
+                                      status=self.status,
+                                      roles=self.roles,
+                                      cpu_info=self.cpu_info,
+                                      memory=self.memory,
+                                      ovs=self.ovs,
+                                      info=self.info)
+
+
+class DeploymentHandler(object):
+
+    EX_OK = os.EX_OK
+    EX_ERROR = os.EX_SOFTWARE
+    FUNCTION_NOT_IMPLEMENTED = "Function not implemented by adapter!"
+
+    def __init__(self,
+                 installer,
+                 installer_ip,
+                 installer_user,
+                 installer_pwd=None,
+                 pkey_file=None):
+
+        self.installer = installer.lower()
+        self.installer_ip = installer_ip
+        self.installer_user = installer_user
+        self.installer_pwd = installer_pwd
+        self.pkey_file = pkey_file
+
+        if pkey_file is not None and not os.path.isfile(pkey_file):
+            raise Exception(
+                'The private key file %s does not exist!' % pkey_file)
+
+        self.installer_connection = ssh_utils.get_ssh_client(
+            hostname=self.installer_ip,
+            username=self.installer_user,
+            password=self.installer_pwd,
+            pkey_file=self.pkey_file)
+
+        if self.installer_connection:
+            self.installer_node = Node(id='',
+                                       ip=installer_ip,
+                                       name=installer,
+                                       status=NodeStatus.STATUS_OK,
+                                       ssh_client=self.installer_connection,
+                                       roles=Role.INSTALLER)
+        else:
+            raise Exception(
+                'Cannot establish connection to the installer node!')
+
+        self.nodes = self.get_nodes()
+
+    @abstractmethod
+    def get_openstack_version(self):
+        '''
+        Returns a string of the openstack version (nova-compute)
+        '''
+        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
+
+    @abstractmethod
+    def get_sdn_version(self):
+        '''
+        Returns a string of the sdn controller and its version, if exists
+        '''
+        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
+
+    @abstractmethod
+    def get_deployment_status(self):
+        '''
+        Returns a string of the status of the deployment
+        '''
+        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
+
+    @abstractmethod
+    def get_nodes(self, options=None):
+        '''
+            Generates a list of all the nodes in the deployment
+        '''
+        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
+
+    def get_installer_node(self):
+        '''
+            Returns the installer node object
+        '''
+        return self.installer_node
+
+    def get_arch(self):
+        '''
+            Returns the architecture of the first compute node found
+        '''
+        arch = None
+        for node in self.nodes:
+            if node.is_compute():
+                arch = node.cpu_info.get('arch', None)
+                if arch:
+                    break
+        return arch
+
+    def get_deployment_info(self):
+        '''
+            Returns an object of type Deployment
+        '''
+        return Deployment(installer=self.installer,
+                          installer_ip=self.installer_ip,
+                          scenario=os.getenv('DEPLOY_SCENARIO', 'Unknown'),
+                          status=self.get_deployment_status(),
+                          pod=os.getenv('NODE_NAME', 'Unknown'),
+                          openstack_version=self.get_openstack_version(),
+                          sdn_controller=self.get_sdn_version(),
+                          nodes=self.nodes)
diff --git a/modules/opnfv/utils/Connection.py b/modules/opnfv/utils/Connection.py
new file mode 100644 (file)
index 0000000..a3be514
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+
+import os
+import time
+
+
+class Connection(object):
+
+    def __init__(self):
+        pass
+
+    def verify_connectivity(self, target):
+        for x in range(0, 10):
+            ping_cmd = ("ping -c 1 -W 1 %s >/dev/null" % target)
+            response = os.system(ping_cmd)
+            if response == 0:
+                return os.EX_OK
+            time.sleep(1)
+        return os.EX_UNAVAILABLE
+
+    def check_internet_access(self, url="www.google.com"):
+        return self.verify_connectivity(url)
diff --git a/modules/opnfv/utils/Credentials.py b/modules/opnfv/utils/Credentials.py
new file mode 100644 (file)
index 0000000..141ecbd
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Usage example:
+#   from opnfv.utils.Credentials import Credentials as credentials
+#   credentials("fuel", "10.20.0.2", "root", "r00tme").fetch('./openrc')
+#
+
+import os
+
+import opnfv.installer_adapters.InstallerHandler as ins_handler
+import opnfv.utils.Connection as con
+import opnfv.utils.OPNFVLogger as logger
+
+
+class Credentials(object):
+
+    def __init__(self, installer, ip, user, password=None):
+        self.installer = installer
+        self.ip = ip
+        self.logger = logger.Logger("Credentials", level="DEBUG").getLogger()
+        self.connection = con.Connection()
+
+        if self.__check_installer_name(self.installer) != os.EX_OK:
+            self.logger.error("Installer %s not supported!" % self.installer)
+            return os.EX_CONFIG
+        else:
+            self.logger.debug("Installer %s supported." % self.installer)
+
+        if self.connection.verify_connectivity(self.ip) != os.EX_OK:
+            self.logger.error("Installer %s not reachable!" % self.ip)
+            return os.EX_UNAVAILABLE
+        else:
+            self.logger.debug("IP %s is reachable!" % self.ip)
+
+        self.logger.debug(
+            "Trying to stablish ssh connection to %s ..." % self.ip)
+        self.handler = ins_handler.InstallerHandler(installer,
+                                                    ip,
+                                                    user,
+                                                    password)
+
+    def __check_installer_name(self, installer):
+        if installer not in ("apex", "compass", "daisy", "fuel", "joid"):
+            return os.EX_CONFIG
+        else:
+            return os.EX_OK
+
+    def __check_path(self, path):
+        try:
+            with open(path, 'a'):
+                os.utime(path, None)
+            return os.EX_OK
+        except IOError as e:
+            self.logger.error(e)
+            return os.EX_IOERR
+
+    def __fetch_creds_apex(self, target_path):
+        # TODO
+        pass
+
+    def __fetch_creds_compass(self, target_path):
+        # TODO
+        pass
+
+    def __fetch_creds_daisy(self, target_path):
+        # TODO
+        pass
+
+    def __fetch_creds_fuel(self, target_path):
+        creds_file = '/root/openrc'
+        try:
+            self.handler.get_file_from_controller(creds_file, target_path)
+        except Exception as e:
+            self.logger.error(
+                "Cannot get %s from controller. %e" % (creds_file, e))
+        pass
+
+    def __fetch_creds_joid(self, target_path):
+        # TODO
+        pass
+
+    def fetch(self, target_path):
+        if self.__check_path(target_path) != os.EX_OK:
+            self.logger.error(
+                "Target path %s does not exist!" % target_path)
+            return os.EX_IOERR
+        else:
+            self.logger.debug("Target path correct.")
+
+        self.logger.info("Fetching credentials from the deployment...")
+        if self.installer == "apex":
+            self.__fetch_creds_apex(target_path)
+        elif self.installer == "compass":
+            self.__fetch_creds_compass(target_path)
+        elif self.installer == "daisy":
+            self.__fetch_creds_daisy(target_path)
+        elif self.installer == "fuel":
+            self.__fetch_creds_fuel(target_path)
+        elif self.installer == "joid":
+            self.__fetch_creds_joid(target_path)
diff --git a/modules/opnfv/utils/OPNFVExceptions.py b/modules/opnfv/utils/OPNFVExceptions.py
new file mode 100644 (file)
index 0000000..03b3ea9
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# This class defines Python OPNFV exceptions
+#
+
+
+class OPNFVException(Exception):
+    def __call__(self, *args):
+        return self.__class__(*(self.args + args))
+
+
+# ************************************
+# Generic
+# ************************************
+class OPNFVSUTNotReachable(OPNFVException):
+    """Target System Under Test is not reachable"""
+    pass
+
+
+class OPNFVCiExecutionError(OPNFVException):
+    """Error occurs during CI exection"""
+    pass
+
+
+class TestDashboardError(OPNFVException):
+    """Impossible to report results to dashboard"""
+    pass
+
+
+class TestReportingError(OPNFVException):
+    """Impossible to report results to reporting"""
+    pass
+
+
+# ************************************
+# Exceptions related to test DB
+# ************************************
+class TestDbNotReachable(OPNFVException):
+    """Test database is not reachable"""
+    pass
+
+
+class UnknownScenario(OPNFVException):
+    """Test scenario is unknown"""
+    pass
+
+
+class UnknownPod(OPNFVException):
+    """Test POD is unknown"""
+    pass
+
+
+class UnknownProject(OPNFVException):
+    """Project is unknown"""
+    pass
+
+
+class UnknownTestCase(OPNFVException):
+    """Test case is unknown"""
+    pass
+
+
+class UnknownVersion(OPNFVException):
+    """Version is unknown"""
+    pass
+
+
+class UnknownInstaller(OPNFVException):
+    """Installer is not supported"""
+    pass
+
+
+# *******************
+# Test project errors
+# *******************
+class FunctestExecutionError(OPNFVException):
+    """Internal Functest error"""
+    pass
+
+
+class YardstickExecutionError(OPNFVException):
+    """Internal Yardstick error"""
+    pass
+
+
+# **********************************
+# Errors related to Feature projects
+# **********************************
+class TestCaseNotRunnable(OPNFVException):
+    """test case incompatible with SUT, scenario, installer"""
+    pass
+
+
+class FeatureTestIntegrationError(OPNFVException):
+    """Impossible to integrate Feature test"""
+    pass
+
+
+class FeatureTestExecutionError(OPNFVException):
+    """Error during Feature test execution"""
+    pass
+
+
+# *********************************
+# Errors related to VNF on boarding
+# *********************************
+class VNFTestNotRunnable(OPNFVException):
+    """VNF test is not compatible with SUT, scenario, installer"""
+    pass
+
+
+class VNFIntegrationError(OPNFVException):
+    """Impossible to integrate the VNF test"""
+    pass
+
+
+class VNFExecutionError(OPNFVException):
+    """Error during VNF test execution"""
+    pass
diff --git a/modules/opnfv/utils/__init__.py b/modules/opnfv/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/utils/constants.py b/modules/opnfv/utils/constants.py
new file mode 100644 (file)
index 0000000..56008c3
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+INSTALLERS = ['apex', 'fuel', 'compass', 'joid', "daisy"]
+VERSIONS = ['arno', 'brahmaputra', 'colorado', 'danube']
+
+EXIT_OK = 0
+EXIT_RUN_ERROR = -1
+EXIT_PUSH_TO_TEST_DB_ERROR = -2
+
+
+class Constants(object):
+    INSTALLERS = ['apex', 'fuel', 'compass', 'joid', "daisy"]
+    VERSIONS = ['arno', 'brahmaputra', 'colorado', 'danube']
+
+    EX_OK = 0
+    EX_RUN_ERROR = -1
+    EX_TEST_FAIL = -2
+    EX_PUSH_RESULT_FAIL = -3
diff --git a/modules/opnfv/utils/ovs_logger.py b/modules/opnfv/utils/ovs_logger.py
new file mode 100644 (file)
index 0000000..7777a9a
--- /dev/null
@@ -0,0 +1,113 @@
+##############################################################################
+# Copyright (c) 2015 Ericsson AB and others.
+# Author: George Paraskevopoulos (geopar@intracom-telecom.com)
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import opnfv.utils.opnfv_logger as OPNFVLogger
+import os
+import time
+import shutil
+
+logger = OPNFVLogger.Logger('ovs_logger').getLogger()
+
+
+class OVSLogger(object):
+
+    def __init__(self, basedir, ft_resdir):
+        self.ovs_dir = basedir
+        self.ft_resdir = ft_resdir
+        self.__mkdir_p(self.ovs_dir)
+        self.__mkdir_p(self.ft_resdir)
+
+    def __mkdir_p(self, dirpath):
+        if not os.path.exists(dirpath):
+            os.makedirs(dirpath)
+
+    def __ssh_host(self, ssh_conn, host_prefix='10.20.0'):
+        try:
+            _, stdout, _ = ssh_conn.exec_command('hostname -I')
+            hosts = stdout.readline().strip().split(' ')
+            found_host = [h for h in hosts if h.startswith(host_prefix)][0]
+            return found_host
+        except Exception as e:
+            logger.error(e)
+
+    def __dump_to_file(self, operation, host, text, timestamp=None):
+        ts = (timestamp if timestamp is not None
+              else time.strftime("%Y%m%d-%H%M%S"))
+        dumpdir = os.path.join(self.ovs_dir, ts)
+        self.__mkdir_p(dumpdir)
+        fname = '{0}_{1}'.format(operation, host)
+        with open(os.path.join(dumpdir, fname), 'w') as f:
+            f.write(text)
+
+    def __remote_cmd(self, ssh_conn, cmd):
+        try:
+            _, stdout, stderr = ssh_conn.exec_command(cmd)
+            errors = stderr.readlines()
+            if len(errors) > 0:
+                host = self.__ssh_host(ssh_conn)
+                logger.error(''.join(errors))
+                raise Exception('Could not execute {0} in {1}'
+                                .format(cmd, host))
+            output = ''.join(stdout.readlines())
+            return output
+        except Exception as e:
+            logger.error('[__remote_command(ssh_client, {0})]: {1}'
+                         .format(cmd, e))
+            return None
+
+    def create_artifact_archive(self):
+        shutil.make_archive(self.ovs_dir,
+                            'zip',
+                            root_dir=os.path.dirname(self.ovs_dir),
+                            base_dir=self.ovs_dir)
+        shutil.copy2('{0}.zip'.format(self.ovs_dir), self.ft_resdir)
+
+    def ofctl_dump_flows(self, ssh_conn, br='br-int',
+                         choose_table=None, timestamp=None):
+        try:
+            cmd = 'ovs-ofctl -OOpenFlow13 dump-flows {0}'.format(br)
+            if choose_table is not None:
+                cmd = '{0} table={1}'.format(cmd, choose_table)
+            output = self.__remote_cmd(ssh_conn, cmd)
+            operation = 'ofctl_dump_flows'
+            host = self.__ssh_host(ssh_conn)
+            self.__dump_to_file(operation, host, output, timestamp=timestamp)
+            return output
+        except Exception as e:
+            logger.error('[ofctl_dump_flows(ssh_client, {0}, {1})]: {2}'
+                         .format(br, choose_table, e))
+            return None
+
+    def vsctl_show(self, ssh_conn, timestamp=None):
+        try:
+            cmd = 'ovs-vsctl show'
+            output = self.__remote_cmd(ssh_conn, cmd)
+            operation = 'vsctl_show'
+            host = self.__ssh_host(ssh_conn)
+            self.__dump_to_file(operation, host, output, timestamp=timestamp)
+            return output
+        except Exception as e:
+            logger.error('[vsctl_show(ssh_client)]: {0}'.format(e))
+            return None
+
+    def dump_ovs_logs(self, controller_clients, compute_clients,
+                      related_error=None, timestamp=None):
+        if timestamp is None:
+            timestamp = time.strftime("%Y%m%d-%H%M%S")
+
+        clients = controller_clients + compute_clients
+        for client in clients:
+            self.ofctl_dump_flows(client, timestamp=timestamp)
+            self.vsctl_show(client, timestamp=timestamp)
+
+        if related_error is not None:
+            dumpdir = os.path.join(self.ovs_dir, timestamp)
+            self.__mkdir_p(dumpdir)
+            with open(os.path.join(dumpdir, 'error'), 'w') as f:
+                f.write(related_error)
diff --git a/modules/opnfv/utils/ssh_utils.py b/modules/opnfv/utils/ssh_utils.py
new file mode 100644 (file)
index 0000000..4c5ff5c
--- /dev/null
@@ -0,0 +1,157 @@
+##############################################################################
+# Copyright (c) 2015 Ericsson AB and others.
+# Authors: George Paraskevopoulos (geopar@intracom-telecom.com)
+#          Jose Lausuch (jose.lausuch@ericsson.com)
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+import os
+import paramiko
+
+from opnfv.utils import opnfv_logger as logger
+
+logger = logger.Logger("SSH utils").getLogger()
+SSH_TIMEOUT = 60
+
+''' Monkey Patch paramiko _custom_start_client '''
+# We are using paramiko 2.1.1 and in the CI in the SFC
+# test we are facing this issue:
+# https://github.com/robotframework/SSHLibrary/issues/158
+# The fix was merged in paramiko 2.1.3 in this PR:
+# https://github.com/robotframework/SSHLibrary/pull/159/files
+# Until we upgrade we can use this monkey patch to work
+# around the issue
+
+
+def _custom_start_client(self, *args, **kwargs):
+    self.banner_timeout = 45
+    self._orig_start_client(*args, **kwargs)
+
+
+paramiko.transport.Transport._orig_start_client = \
+    paramiko.transport.Transport.start_client
+paramiko.transport.Transport.start_client = _custom_start_client
+''' Monkey Patch paramiko _custom_start_client '''
+
+
+def get_ssh_client(hostname,
+                   username,
+                   password=None,
+                   proxy=None,
+                   pkey_file=None):
+    client = None
+    try:
+        if proxy is None:
+            client = paramiko.SSHClient()
+        else:
+            client = ProxyHopClient()
+            client.configure_jump_host(proxy['ip'],
+                                       proxy['username'],
+                                       proxy['password'])
+        if client is None:
+            raise Exception('Could not connect to client')
+
+        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        if pkey_file is not None:
+            key = paramiko.RSAKey.from_private_key_file(pkey_file)
+            client.load_system_host_keys()
+            client.connect(hostname,
+                           username=username,
+                           pkey=key,
+                           timeout=SSH_TIMEOUT)
+        else:
+            client.connect(hostname,
+                           username=username,
+                           password=password,
+                           timeout=SSH_TIMEOUT)
+
+        return client
+    except Exception as e:
+        logger.error(e)
+        return None
+
+
+def get_file(ssh_conn, src, dest):
+    try:
+        sftp = ssh_conn.open_sftp()
+        sftp.get(src, dest)
+        return True
+    except Exception as e:
+        logger.error("Error [get_file(ssh_conn, '%s', '%s']: %s" %
+                     (src, dest, e))
+        return None
+
+
+def put_file(ssh_conn, src, dest):
+    try:
+        sftp = ssh_conn.open_sftp()
+        sftp.put(src, dest)
+        return True
+    except Exception as e:
+        logger.error("Error [put_file(ssh_conn, '%s', '%s']: %s" %
+                     (src, dest, e))
+        return None
+
+
+class ProxyHopClient(paramiko.SSHClient):
+    '''
+    Connect to a remote server using a proxy hop
+    '''
+
+    def __init__(self, *args, **kwargs):
+        self.proxy_ssh = None
+        self.proxy_transport = None
+        self.proxy_channel = None
+        self.proxy_ip = None
+        self.proxy_ssh_key = None
+        self.local_ssh_key = os.path.join(os.getcwd(), 'id_rsa')
+        super(ProxyHopClient, self).__init__(*args, **kwargs)
+
+    def configure_jump_host(self, jh_ip, jh_user, jh_pass,
+                            jh_ssh_key='/root/.ssh/id_rsa'):
+        self.proxy_ip = jh_ip
+        self.proxy_ssh_key = jh_ssh_key
+        self.proxy_ssh = paramiko.SSHClient()
+        self.proxy_ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        self.proxy_ssh.connect(jh_ip,
+                               username=jh_user,
+                               password=jh_pass,
+                               timeout=SSH_TIMEOUT)
+        self.proxy_transport = self.proxy_ssh.get_transport()
+
+    def connect(self, hostname, port=22, username='root', password=None,
+                pkey=None, key_filename=None, timeout=None, allow_agent=True,
+                look_for_keys=True, compress=False, sock=None, gss_auth=False,
+                gss_kex=False, gss_deleg_creds=True, gss_host=None,
+                banner_timeout=None):
+        try:
+            if self.proxy_ssh is None:
+                raise Exception('You must configure the jump '
+                                'host before calling connect')
+
+            get_file_res = get_file(self.proxy_ssh,
+                                    self.proxy_ssh_key,
+                                    self.local_ssh_key)
+            if get_file_res is None:
+                raise Exception('Could\'t fetch SSH key from jump host')
+            proxy_key = (paramiko.RSAKey
+                         .from_private_key_file(self.local_ssh_key))
+
+            self.proxy_channel = self.proxy_transport.open_channel(
+                "direct-tcpip",
+                (hostname, 22),
+                (self.proxy_ip, 22))
+
+            self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+            super(ProxyHopClient, self).connect(hostname,
+                                                username=username,
+                                                pkey=proxy_key,
+                                                sock=self.proxy_channel,
+                                                timeout=timeout)
+            os.remove(self.local_ssh_key)
+        except Exception as e:
+            logger.error(e)
diff --git a/modules/requirements.txt b/modules/requirements.txt
new file mode 100644 (file)
index 0000000..1eaf8d0
--- /dev/null
@@ -0,0 +1,3 @@
+paramiko>=2.0.1
+mock==1.3.0
+requests==2.9.1
diff --git a/modules/run_unit_tests.sh b/modules/run_unit_tests.sh
new file mode 100755 (executable)
index 0000000..df511ce
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/bash
+set -o errexit
+set -o pipefail
+
+# Either Workspace is set (CI)
+if [ -z $WORKSPACE ]
+then
+    WORKSPACE="."
+fi
+
+
+# ***************
+# Run unit tests
+# ***************
+echo "Running unit tests..."
+
+# start vitual env
+virtualenv $WORKSPACE/modules_venv
+source $WORKSPACE/modules_venv/bin/activate
+
+# install python packages
+easy_install -U setuptools
+easy_install -U pip
+pip install $WORKSPACE
+
+
+# unit tests
+nosetests --with-xunit \
+         --cover-package=opnfv \
+         --with-coverage \
+         --cover-xml \
+         --cover-html \
+         tests/unit
+rc=$?
+
+deactivate
diff --git a/modules/setup.py b/modules/setup.py
new file mode 100644 (file)
index 0000000..8ac5cea
--- /dev/null
@@ -0,0 +1,25 @@
+##############################################################################
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from setuptools import setup, find_packages
+
+
+setup(
+    name="opnfv",
+    version="danube",
+    packages=find_packages(),
+    include_package_data=True,
+    package_data={
+    },
+    url="https://www.opnfv.org",
+    install_requires=["paramiko>=2.0.1",
+                      "mock==1.3.0",
+                      "nose==1.3.7",
+                      "coverage==4.1",
+                      "requests==2.9.1"]
+)
diff --git a/modules/test-requirements.txt b/modules/test-requirements.txt
new file mode 100644 (file)
index 0000000..99d7f13
--- /dev/null
@@ -0,0 +1,6 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+
+nose
+coverage
diff --git a/modules/tests/__init__.py b/modules/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/tests/unit/__init__.py b/modules/tests/unit/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/tests/unit/utils/__init__.py b/modules/tests/unit/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/tests/unit/utils/test_OPNFVExceptions.py b/modules/tests/unit/utils/test_OPNFVExceptions.py
new file mode 100644 (file)
index 0000000..fca927b
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0import requests
+import unittest
+import requests
+
+from opnfv.utils import OPNFVExceptions
+
+
+def base_function():
+    raise OPNFVExceptions.TestDbNotReachable('Test database is not reachable')
+
+
+def base_function_wrong():
+    raise OPNFVExceptions.NotSelfDefinedException
+
+
+def db_connectivity():
+    url = 'http://testresults.opnfv2.org/test/api/v1/projects/functest/cases'
+    r = requests.get(url)
+    if r.status_code is not 200:
+        raise OPNFVExceptions.TestDbNotReachable('Database not found')
+
+
+def project_unknown():
+    url = 'http://testresults.opnfv.org/test/api/v1/projects/functest2/cases'
+    r = requests.get(url)
+    if len(r.json()['testcases']) is 0:
+        raise OPNFVExceptions.UnknownProject
+
+
+class TestBasicRaise(unittest.TestCase):
+    def test(self):
+        with self.assertRaises(Exception) as context:
+            base_function()
+        self.assertTrue('Test database is not reachable' in context.exception)
+
+
+class TestWrongRaise(unittest.TestCase):
+    def test(self):
+        try:
+            base_function_wrong()
+        except OPNFVExceptions.OPNFVException:
+            assert(False)
+        except AttributeError:
+            assert(True)
+
+
+class TestCaseDBNotReachable(unittest.TestCase):
+    def test(self):
+        with self.assertRaises(Exception) as context:
+            db_connectivity()
+        self.assertTrue('Database not found' in context.exception)
+
+
+class TestUnkownProject(unittest.TestCase):
+    def test(self):
+        try:
+            project_unknown()
+        except OPNFVExceptions.TestDashboardError:
+            # should not be there
+            assert(False)
+        except OPNFVExceptions.UnknownProject:
+            assert(True)
+        except Exception:
+            assert(False)
+
+if __name__ == '__main__':
+    unittest.main()
index 0ba49d4..dc1417a 100644 (file)
@@ -31,7 +31,7 @@ Please follow that steps:
     cd /opt/bifrost
     sudo ./scripts/destroy-env.sh
 
-8. Run deployment script to spin up 3 vms with bifrost: jumphost, controller and compute::
+8. Run deployment script to spin up 3 vms with bifrost: xcimaster, controller and compute::
 
     cd /opt/bifrost
     sudo ./scripts/test-bifrost-deployment.sh
diff --git a/prototypes/bifrost/playbooks/inventory/group_vars/baremetal b/prototypes/bifrost/playbooks/inventory/group_vars/baremetal
new file mode 100644 (file)
index 0000000..008b04d
--- /dev/null
@@ -0,0 +1,53 @@
+---
+# The ironic API URL for bifrost operations.  Defaults to localhost.
+# ironic_url: "http://localhost:6385/"
+
+# The network interface that bifrost will be operating on.  Defaults
+# to virbr0 in roles, can be overridden here.
+# network_interface: "virbr0"
+
+# The path to the SSH key to be utilized for testing and burn-in
+# to configuration drives. When set, it should be set in both baremetal
+# and localhost groups, however this is only an override to the default.
+
+# workaround for opnfv ci until we can fix non-root use
+ssh_public_key_path: "/root/.ssh/id_rsa.pub"
+
+# Normally this user should be root, however if cirros is used,
+# a user may wish to define a specific user for testing VM
+# connectivity during a test sequence
+testing_user: root
+
+# The default port to download files via.  Required for IPA URL generation.
+# Presently the defaults are located in the roles, however if changed both
+# the localhost and baremetal group files must be updated.
+# file_url_port: 8080
+
+# IPA Image parameters.  If these are changed, they must be changed in
+# Both localhost and baremetal groups.  Presently the defaults
+# in each role should be sufficent for proper operation.
+# ipa_kernel: "{{http_boot_folder}}/coreos_production_pxe.vmlinuz"
+# ipa_ramdisk: "{{http_boot_folder}}/coreos_production_pxe_image-oem.cpio.gz"
+# ipa_kernel_url: "http://{{ hostvars[inventory_hostname]['ansible_' + network_interface]['ipv4']['address'] }}:{{file_url_port}}/coreos_production_pxe.vmlinuz"
+# ipa_ramdisk_url: "http://{{ hostvars[inventory_hostname]['ansible_' + network_interface]['ipv4']['address'] }}:{{file_url_port}}/coreos_production_pxe_image-oem.cpio.gz"
+
+# The http_boot_folder defines the root folder for the webserver.
+# If this setting is changed, it must be applied to both the baremetal
+# and localhost groups. Presently the role defaults are set to the value
+# below.
+# http_boot_folder: /httpboot
+
+# The settings for the name of the image to be deployed along with the
+# on disk location are below.  If changed, these settings must be applied
+# to both the baremetal and localhost groups.  If the file is already on
+# disk, then the image generation will not take place, otherwise an image
+# will be generated using diskimage-builder.
+# deploy_image_filename: "deployment_image.qcow2"
+# deploy_image: "{{http_boot_folder}}/{{deploy_image_filename}}"
+
+# Under normal circumstances, the os_ironic_node module does not wait for
+# the node to reach active state before continuing with the deployment
+# process.  This means we may have to timeout, to figure out a deployment
+# failed.  Change wait_for_node_deploy to true to cause bifrost to wait for
+# Ironic to show the instance in Active state.
+wait_for_node_deploy: false
@@ -53,7 +53,8 @@
       dib_imagename: "{{deploy_image}}"
       dib_os_element: "{{ lookup('env','DIB_OS_ELEMENT') }}"
       dib_os_release: "{{ lookup('env', 'DIB_OS_RELEASE') }}"
-      dib_elements: "vm serial-console simple-init devuser infra-cloud-bridge puppet growroot {{ extra_dib_elements|default('') }}"
+      extra_dib_elements: "{{ lookup('env', 'EXTRA_DIB_ELEMENTS') | default('') }}"
+      dib_elements: "vm enable-serial-console simple-init devuser growroot {{ extra_dib_elements }}"
       dib_packages: "{{ lookup('env', 'DIB_OS_PACKAGES') }}"
       when: create_image_via_dib | bool == true and transform_boot_image | bool == false
   environment:
@@ -7,40 +7,45 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-
 set -eux
 set -o pipefail
+
 export PYTHONUNBUFFERED=1
 SCRIPT_HOME="$(cd "$(dirname "$0")" && pwd)"
 BIFROST_HOME=$SCRIPT_HOME/..
 ANSIBLE_INSTALL_ROOT=${ANSIBLE_INSTALL_ROOT:-/opt/stack}
+ANSIBLE_VERBOSITY=${ANSIBLE_VERBOSITY-"-vvvv"}
 ENABLE_VENV="false"
 USE_DHCP="false"
 USE_VENV="false"
 BUILD_IMAGE=true
-PROVISION_WAIT_TIMEOUT=${PROVISION_WAIT_TIMEOUT:-2400}
-
-# Set defaults for ansible command-line options to drive the different
-# tests.
-
-# NOTE(TheJulia/cinerama): The variables defined on the command line
-# for the default and DHCP tests are to drive the use of Cirros as the
-# deployed operating system, and as such sets the test user to cirros,
-# and writes a debian style interfaces file out to the configuration
-# drive as cirros does not support the network_info.json format file
-# placed in the configuration drive. The "build image" test does not
-# use cirros.
-
-TEST_VM_NUM_NODES=3
-export TEST_VM_NODE_NAMES="jumphost.opnfvlocal controller00.opnfvlocal compute00.opnfvlocal"
-export VM_DOMAIN_TYPE="kvm"
-export VM_CPU=4
-export VM_DISK=100
-TEST_PLAYBOOK="test-bifrost-infracloud.yaml"
+PROVISION_WAIT_TIMEOUT=${PROVISION_WAIT_TIMEOUT:-3600}
+
+# Ensure the right inventory files is used based on branch
+CURRENT_BIFROST_BRANCH=$(git rev-parse --abbrev-ref HEAD)
+if [ $CURRENT_BIFROST_BRANCH = "master" ]; then
+    BAREMETAL_DATA_FILE=${BAREMETAL_DATA_FILE:-'/tmp/baremetal.json'}
+    INVENTORY_FILE_FORMAT="baremetal_json_file"
+else
+    BAREMETAL_DATA_FILE=${BAREMETAL_DATA_FILE:-'/tmp/baremetal.csv'}
+    INVENTORY_FILE_FORMAT="baremetal_csv_file"
+fi
+export BIFROST_INVENTORY_SOURCE=$BAREMETAL_DATA_FILE
+
+# Default settings for VMs
+export TEST_VM_NUM_NODES=${TEST_VM_NUM_NODES:-3}
+export TEST_VM_NODE_NAMES=${TEST_VM_NODE_NAMES:-"opnfv controller00 compute00"}
+export VM_DOMAIN_TYPE=${VM_DOMAIN_TYPE:-kvm}
+export VM_CPU=${VM_CPU:-4}
+export VM_DISK=${VM_DISK:-100}
+export VM_MEMORY_SIZE=${VM_MEMORY_SIZE:-8192}
+export VM_DISK_CACHE=${VM_DISK_CACHE:-unsafe}
+
+# Settings for bifrost
+TEST_PLAYBOOK="opnfv-virtual.yaml"
 USE_INSPECTOR=true
 USE_CIRROS=false
 TESTING_USER=root
-VM_MEMORY_SIZE="8192"
 DOWNLOAD_IPA=true
 CREATE_IPA_IMAGE=false
 INSPECT_NODES=true
@@ -48,60 +53,60 @@ INVENTORY_DHCP=false
 INVENTORY_DHCP_STATIC_IP=false
 WRITE_INTERFACES_FILE=true
 
-# Set BIFROST_INVENTORY_SOURCE
-export BIFROST_INVENTORY_SOURCE=/tmp/baremetal.csv
-
-# DIB custom elements path
-export ELEMENTS_PATH=/usr/share/diskimage-builder/elements:/opt/puppet-infracloud/files/elements
-
-# settings for console access
+# Settings for console access
 export DIB_DEV_USER_PWDLESS_SUDO=yes
 export DIB_DEV_USER_PASSWORD=devuser
 
-# settings for distro: trusty/ubuntu-minimal, 7/centos7
+# Settings for distro: trusty/ubuntu-minimal, 7/centos7, 42.2/suse
 export DIB_OS_RELEASE=${DIB_OS_RELEASE:-trusty}
 export DIB_OS_ELEMENT=${DIB_OS_ELEMENT:-ubuntu-minimal}
 
-# for centos 7: "openssh-server,vim,less,bridge-utils,iputils,rsyslog,curl"
-export DIB_OS_PACKAGES=${DIB_OS_PACKAGES:-"openssh-server,vlan,vim,less,bridge-utils,language-pack-en,iputils-ping,rsyslog,curl"}
+# DIB OS packages
+export DIB_OS_PACKAGES=${DIB_OS_PACKAGES:-"vlan,vim,less,bridge-utils,language-pack-en,iputils-ping,rsyslog,curl"}
+
+# Additional dib elements
+export EXTRA_DIB_ELEMENTS=${EXTRA_DIB_ELEMENTS:-"openssh-server"}
 
 # Source Ansible
-# NOTE(TheJulia): Ansible stable-1.9 source method tosses an error deep
-# under the hood which -x will detect, so for this step, we need to suspend
-# and then re-enable the feature.
 set +x +o nounset
 $SCRIPT_HOME/env-setup.sh
 source ${ANSIBLE_INSTALL_ROOT}/ansible/hacking/env-setup
 ANSIBLE=$(which ansible-playbook)
 set -x -o nounset
 
+logs_on_exit() {
+    $SCRIPT_HOME/collect-test-info.sh
+}
+trap logs_on_exit EXIT
+
 # Change working directory
 cd $BIFROST_HOME/playbooks
 
 # Syntax check of dynamic inventory test path
 for task in syntax-check list-tasks; do
-    ${ANSIBLE} -vvvv \
+    ${ANSIBLE} ${ANSIBLE_VERBOSITY} \
            -i inventory/localhost \
            test-bifrost-create-vm.yaml \
            --${task}
-    ${ANSIBLE} -vvvv \
+    ${ANSIBLE} ${ANSIBLE_VERBOSITY} \
            -i inventory/localhost \
            ${TEST_PLAYBOOK} \
            --${task} \
            -e testing_user=${TESTING_USER}
 done
 
-# Create the test VMS
-${ANSIBLE} -vvvv \
+# Create the VMS
+${ANSIBLE} ${ANSIBLE_VERBOSITY} \
        -i inventory/localhost \
        test-bifrost-create-vm.yaml \
        -e test_vm_num_nodes=${TEST_VM_NUM_NODES} \
        -e test_vm_memory_size=${VM_MEMORY_SIZE} \
        -e enable_venv=${ENABLE_VENV} \
-       -e test_vm_domain_type=${VM_DOMAIN_TYPE}
+       -e test_vm_domain_type=${VM_DOMAIN_TYPE} \
+       -e ${INVENTORY_FILE_FORMAT}=${BAREMETAL_DATA_FILE}
 
-# Execute the installation and VM startup test.
-${ANSIBLE} -vvvv \
+# Execute the installation and VM startup test
+${ANSIBLE} ${ANSIBLE_VERBOSITY} \
     -i inventory/bifrost_inventory.py \
     ${TEST_PLAYBOOK} \
     -e use_cirros=${USE_CIRROS} \
@@ -120,11 +125,9 @@ ${ANSIBLE} -vvvv \
 EXITCODE=$?
 
 if [ $EXITCODE != 0 ]; then
-    echo "****************************"
-    echo "Test failed. See logs folder"
-    echo "****************************"
+    echo "************************************"
+    echo "Provisioning failed. See logs folder"
+    echo "************************************"
 fi
 
-$SCRIPT_HOME/collect-test-info.sh
-
 exit $EXITCODE
index cdc55df..d570f10 100755 (executable)
@@ -14,24 +14,29 @@ if [[ $(whoami) != "root" ]]; then
     exit 1
 fi
 
-virsh destroy jumphost.opnfvlocal || true
-virsh destroy controller00.opnfvlocal || true
-virsh destroy compute00.opnfvlocal || true
-virsh undefine jumphost.opnfvlocal || true
-virsh undefine controller00.opnfvlocal || true
-virsh undefine compute00.opnfvlocal || true
-
-service ironic-conductor stop
-
-echo "removing from database"
-mysql -u root ironic --execute "truncate table ports;"
-mysql -u root ironic --execute "delete from node_tags;"
-mysql -u root ironic --execute "delete from nodes;"
-mysql -u root ironic --execute "delete from conductors;"
+# Start fresh
+rm -rf /opt/stack
+
+# Delete all libvirt VMs and hosts from vbmc (look for a port number)
+for vm in $(vbmc list | awk '/[0-9]/{{ print $2 }}'); do
+    virsh destroy $vm || true
+    virsh undefine $vm || true
+    vbmc delete $vm
+done
+
+service ironic-conductor stop || true
+
+echo "removing inventory files created by previous builds"
+rm -rf /tmp/baremetal.*
+
+echo "removing ironic database"
+if $(which mysql &> /dev/null); then
+    mysql -u root ironic --execute "drop database ironic;"
+fi
 echo "removing leases"
 [[ -e /var/lib/misc/dnsmasq/dnsmasq.leases ]] && > /var/lib/misc/dnsmasq/dnsmasq.leases
 echo "removing logs"
-rm -rf /var/log/libvirt/baremetal_logs/*.log
+rm -rf /var/log/libvirt/baremetal_logs/*
 
 # clean up dib images only if requested explicitly
 CLEAN_DIB_IMAGES=${CLEAN_DIB_IMAGES:-false}
@@ -48,6 +53,6 @@ rm -rf /var/lib/libvirt/images/*.qcow2
 echo "restarting services"
 service dnsmasq restart || true
 service libvirtd restart
-service ironic-api restart
-service ironic-conductor start
-service ironic-inspector restart
+service ironic-api restart || true
+service ironic-conductor start || true
+service ironic-inspector restart || true
diff --git a/prototypes/openstack-ansible/README.md b/prototypes/openstack-ansible/README.md
new file mode 100644 (file)
index 0000000..34c1d0d
--- /dev/null
@@ -0,0 +1,48 @@
+===============================
+How to deploy OpenStack-Ansible
+===============================
+The script and playbooks defined on this repo will deploy an OpenStack
+cloud based on OpenStack-Ansible.
+It needs to be combined with Bifrost. You need use Bifrost to provide six VMs.
+To learn about how to use Bifrost, you can read the document on
+[/opt/releng/prototypes/bifrost/README.md].
+
+Minimal requirements:
+1. You will need to have a least 150G free space for the partition on where
+   "/var/lib/libvirt/images/" lives.
+2. each vm needs to have at least 8 vCPU, 12 GB RAM, 60 GB HDD.
+
+After provisioning the six VMs please follow that steps:
+
+1.Run the script to deploy OpenStack
+  cd /opt/releng/prototypes/openstack-ansible/scripts/
+  sudo ./osa_deploy.sh
+It will take a lot of time. When the deploy is successful, you will see the
+message "OpenStack deployed successfully".
+
+2.To verify the OpenStack operation
+  2.1 ssh into the controller::
+      ssh 192.168.122.3
+  2.2 Enter into the lxc container::
+      lxcname=$(lxc-ls | grep utility)
+      lxc-attach -n $lxcname
+  2.3 Verify the OpenStack API::
+      source /root/openrc
+      openstack user list
+
+This will show the following output::
++----------------------------------+--------------------+
+| ID                               | Name               |
++----------------------------------+--------------------+
+| 056f8fe41336435991fd80872731cada | aodh               |
+| 308f6436e68f40b49d3b8e7ce5c5be1e | glance             |
+| 351b71b43a66412d83f9b3cd75485875 | nova               |
+| 511129e053394aea825cce13b9f28504 | ceilometer         |
+| 5596f71319d44c8991fdc65f3927b62e | gnocchi            |
+| 586f49e3398a4c47a2f6fe50135d4941 | stack_domain_admin |
+| 601b329e6b1d427f9a1e05ed28753497 | heat               |
+| 67fe383b94964a4781345fbcc30ae434 | cinder             |
+| 729bb08351264d729506dad84ed3ccf0 | admin              |
+| 9f2beb2b270940048fe6844f0b16281e | neutron            |
+| fa68f86dd1de4ddbbb7415b4d9a54121 | keystone           |
++----------------------------------+--------------------+
diff --git a/prototypes/openstack-ansible/file/cinder.yml b/prototypes/openstack-ansible/file/cinder.yml
new file mode 100644 (file)
index 0000000..e40b392
--- /dev/null
@@ -0,0 +1,13 @@
+---
+# This file contains an example to show how to set
+# the cinder-volume service to run in a container.
+#
+# Important note:
+# When using LVM or any iSCSI-based cinder backends, such as NetApp with
+# iSCSI protocol, the cinder-volume service *must* run on metal.
+# Reference: https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/1226855
+
+container_skel:
+  cinder_volumes_container:
+    properties:
+      is_metal: false
diff --git a/prototypes/openstack-ansible/file/exports b/prototypes/openstack-ansible/file/exports
new file mode 100644 (file)
index 0000000..315f79d
--- /dev/null
@@ -0,0 +1,12 @@
+# /etc/exports: the access control list for filesystems which may be exported
+#               to NFS clients.  See exports(5).
+#
+# Example for NFSv2 and NFSv3:
+# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
+#
+# Example for NFSv4:
+# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
+# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
+#
+/images         *(rw,sync,no_subtree_check,no_root_squash)
+
diff --git a/prototypes/openstack-ansible/file/modules b/prototypes/openstack-ansible/file/modules
new file mode 100644 (file)
index 0000000..60a517f
--- /dev/null
@@ -0,0 +1,8 @@
+# /etc/modules: kernel modules to load at boot time.
+#
+# This file contains the names of kernel modules that should be loaded
+# at boot time, one per line. Lines beginning with "#" are ignored.
+# Parameters can be specified after the module name.
+
+bonding
+8021q
diff --git a/prototypes/openstack-ansible/file/openstack_user_config.yml b/prototypes/openstack-ansible/file/openstack_user_config.yml
new file mode 100644 (file)
index 0000000..43e88c0
--- /dev/null
@@ -0,0 +1,278 @@
+---
+cidr_networks:
+  container: 172.29.236.0/22
+  tunnel: 172.29.240.0/22
+  storage: 172.29.244.0/22
+
+used_ips:
+  - "172.29.236.1,172.29.236.50"
+  - "172.29.240.1,172.29.240.50"
+  - "172.29.244.1,172.29.244.50"
+  - "172.29.248.1,172.29.248.50"
+
+global_overrides:
+  internal_lb_vip_address: 172.29.236.222
+  external_lb_vip_address: 192.168.122.220
+  tunnel_bridge: "br-vxlan"
+  management_bridge: "br-mgmt"
+  provider_networks:
+    - network:
+        container_bridge: "br-mgmt"
+        container_type: "veth"
+        container_interface: "eth1"
+        ip_from_q: "container"
+        type: "raw"
+        group_binds:
+          - all_containers
+          - hosts
+        is_container_address: true
+        is_ssh_address: true
+    - network:
+        container_bridge: "br-vxlan"
+        container_type: "veth"
+        container_interface: "eth10"
+        ip_from_q: "tunnel"
+        type: "vxlan"
+        range: "1:1000"
+        net_name: "vxlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth12"
+        host_bind_override: "eth12"
+        type: "flat"
+        net_name: "flat"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth11"
+        type: "vlan"
+        range: "1:1"
+        net_name: "vlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-storage"
+        container_type: "veth"
+        container_interface: "eth2"
+        ip_from_q: "storage"
+        type: "raw"
+        group_binds:
+          - glance_api
+          - cinder_api
+          - cinder_volume
+          - nova_compute
+
+# ##
+# ## Infrastructure
+# ##
+
+# galera, memcache, rabbitmq, utility
+shared-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# repository (apt cache, python packages, etc)
+repo-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# load balancer
+# Ideally the load balancer should not use the Infrastructure hosts.
+# Dedicated hardware is best for improved performance and security.
+haproxy_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# rsyslog server
+# log_hosts:
+# log1:
+#  ip: 172.29.236.14
+
+# ##
+# ## OpenStack
+# ##
+
+# keystone
+identity_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# cinder api services
+storage-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# glance
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+image_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.15"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+  controller01:
+    ip: 172.29.236.12
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.15"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+  controller02:
+    ip: 172.29.236.13
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.15"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+
+# nova api, conductor, etc services
+compute-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# heat
+orchestration_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# horizon
+dashboard_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# neutron server, agents (L3, etc)
+network_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# ceilometer (telemetry API)
+metering-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# aodh (telemetry alarm service)
+metering-alarm_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# gnocchi (telemetry metrics storage)
+metrics_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# nova hypervisors
+compute_hosts:
+  compute00:
+    ip: 172.29.236.14
+  compute01:
+    ip: 172.29.236.15
+
+# ceilometer compute agent (telemetry)
+metering-compute_hosts:
+  compute00:
+    ip: 172.29.236.14
+  compute01:
+    ip: 172.29.236.15
+# cinder volume hosts (NFS-backed)
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+storage_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.11"
+  controller01:
+    ip: 172.29.236.12
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.12"
+  controller02:
+    ip: 172.29.236.13
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.13"
diff --git a/prototypes/openstack-ansible/file/opnfv-setup-openstack.yml b/prototypes/openstack-ansible/file/opnfv-setup-openstack.yml
new file mode 100644 (file)
index 0000000..aacdeff
--- /dev/null
@@ -0,0 +1,34 @@
+---
+# Copyright 2014, Rackspace US, Inc.
+#
+# 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.
+
+- include: os-keystone-install.yml
+- include: os-glance-install.yml
+- include: os-cinder-install.yml
+- include: os-nova-install.yml
+- include: os-neutron-install.yml
+- include: os-heat-install.yml
+- include: os-horizon-install.yml
+- include: os-ceilometer-install.yml
+- include: os-aodh-install.yml
+#NOTE(stevelle) Ensure Gnocchi identities exist before Swift
+- include: os-gnocchi-install.yml
+  when:
+    - gnocchi_storage_driver is defined
+    - gnocchi_storage_driver == 'swift'
+  vars:
+    gnocchi_identity_only: True
+- include: os-swift-install.yml
+- include: os-gnocchi-install.yml
+- include: os-ironic-install.yml
diff --git a/prototypes/openstack-ansible/file/user_variables.yml b/prototypes/openstack-ansible/file/user_variables.yml
new file mode 100644 (file)
index 0000000..65cbcc1
--- /dev/null
@@ -0,0 +1,27 @@
+---
+# Copyright 2014, Rackspace US, Inc.
+#
+# 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.
+
+# ##
+# ## This file contains commonly used overrides for convenience. Please inspect
+# ## the defaults for each role to find additional override options.
+# ##
+
+# # Debug and Verbose options.
+debug: false
+
+haproxy_keepalived_external_vip_cidr: "192.168.122.220/32"
+haproxy_keepalived_internal_vip_cidr: "172.29.236.222/32"
+haproxy_keepalived_external_interface: br-vlan
+haproxy_keepalived_internal_interface: br-mgmt
diff --git a/prototypes/openstack-ansible/playbooks/configure-targethosts.yml b/prototypes/openstack-ansible/playbooks/configure-targethosts.yml
new file mode 100644 (file)
index 0000000..538fe17
--- /dev/null
@@ -0,0 +1,61 @@
+---
+- hosts: all
+  remote_user: root
+  vars_files:
+    - ../var/ubuntu.yml
+  tasks:
+    - name: add public key to host
+      copy:
+        src: ../file/authorized_keys
+        dest: /root/.ssh/authorized_keys
+    - name: configure modules
+      copy:
+        src: ../file/modules
+        dest: /etc/modules
+
+- hosts: controller
+  remote_user: root
+  vars_files:
+    - ../var/ubuntu.yml
+  tasks:
+    - name: configure network
+      template:
+        src: ../template/bifrost/controller.interface.j2
+        dest: /etc/network/interfaces
+      notify:
+        - restart network service
+  handlers:
+    - name: restart network service
+      shell: "/sbin/ifconfig ens3 0 &&/sbin/ifdown -a && /sbin/ifup -a"
+
+- hosts: compute
+  remote_user: root
+  vars_files:
+    - ../var/ubuntu.yml
+  tasks:
+    - name: configure network
+      template:
+        src: ../template/bifrost/compute.interface.j2
+        dest: /etc/network/interfaces
+      notify:
+        - restart network service
+  handlers:
+    - name: restart network service
+      shell: "/sbin/ifconfig ens3 0 &&/sbin/ifdown -a && /sbin/ifup -a"
+
+- hosts: compute01
+  remote_user: root
+  tasks:
+    - name: make nfs dir
+      file: "dest=/images mode=0777 state=directory"
+    - name: configure sdrvice
+      shell: "echo 'nfs        2049/tcp' >>  /etc/services && echo 'nfs        2049/udp' >>  /etc/services"
+    - name: configure NFS
+      copy:
+        src: ../file/exports
+        dest: /etc/exports
+      notify:
+        - restart nfs service
+  handlers:
+    - name: restart nfs service
+      service: name=nfs-kernel-server state=restarted
diff --git a/prototypes/openstack-ansible/playbooks/configure-xcimaster.yml b/prototypes/openstack-ansible/playbooks/configure-xcimaster.yml
new file mode 100644 (file)
index 0000000..fbbde64
--- /dev/null
@@ -0,0 +1,66 @@
+---
+- hosts: xcimaster
+  remote_user: root
+  vars_files:
+    - ../var/ubuntu.yml
+  tasks:
+    - name: generate SSH keys
+      shell: ssh-keygen -b 2048 -t rsa -f /root/.ssh/id_rsa -q -N ""
+      args:
+        creates: /root/.ssh/id_rsa
+    - name: fetch public key
+      fetch: src="/root/.ssh/id_rsa.pub" dest="/"
+    - name: remove openstack-ansible directories
+      file:
+        path={{ item }}
+        state=absent
+        recurse=no
+      with_items:
+        - "{{OSA_PATH}}"
+        - "{{OSA_ETC_PATH}}"
+    - name: clone openstack-ansible
+      git:
+        repo: "{{OSA_URL}}"
+        dest: "{{OSA_PATH}}"
+        version: "{{OPENSTACK_OSA_VERSION}}"
+    - name: copy opnfv-setup-openstack.yml to /opt/openstack-ansible/playbooks
+      copy:
+        src: ../file/opnfv-setup-openstack.yml
+        dest: "{{OSA_PATH}}/playbooks/opnfv-setup-openstack.yml"
+    - name: copy /opt/openstack-ansible/etc/openstack_deploy to /etc/openstack_deploy
+      shell: "/bin/cp -rf {{OSA_PATH}}/etc/openstack_deploy {{OSA_ETC_PATH}}"
+    - name: bootstrap
+      command: "/bin/bash ./scripts/bootstrap-ansible.sh"
+      args:
+        chdir: "{{OSA_PATH}}"
+    - name: generate password token
+      command: "python pw-token-gen.py --file /etc/openstack_deploy/user_secrets.yml"
+      args:
+        chdir: /opt/openstack-ansible/scripts/
+    - name: copy openstack_user_config.yml to /etc/openstack_deploy
+      copy:
+        src: ../file/openstack_user_config.yml
+        dest: "{{OSA_ETC_PATH}}/openstack_user_config.yml"
+    - name: copy cinder.yml to /etc/openstack_deploy/env.d
+      copy:
+        src: ../file/cinder.yml
+        dest: "{{OSA_ETC_PATH}}/env.d/cinder.yml"
+    - name: copy user_variables.yml to /etc/openstack_deploy/
+      copy:
+        src: ../file/user_variables.yml
+        dest: "{{OSA_ETC_PATH}}/user_variables.yml"
+    - name: configure network
+      template:
+        src: ../template/bifrost/controller.interface.j2
+        dest: /etc/network/interfaces
+      notify:
+        - restart network service
+  handlers:
+    - name: restart network service
+      shell: "/sbin/ifconfig ens3 0 &&/sbin/ifdown -a && /sbin/ifup -a"
+
+- hosts: localhost
+  remote_user: root
+  tasks:
+    - name: Generate authorized_keys
+      shell: "/bin/cat /xcimaster/root/.ssh/id_rsa.pub >> ../file/authorized_keys"
diff --git a/prototypes/openstack-ansible/playbooks/inventory b/prototypes/openstack-ansible/playbooks/inventory
new file mode 100644 (file)
index 0000000..d3768f5
--- /dev/null
@@ -0,0 +1,11 @@
+[xcimaster]
+xcimaster ansible_ssh_host=192.168.122.2
+
+[controller]
+controller00 ansible_ssh_host=192.168.122.3
+controller01 ansible_ssh_host=192.168.122.4
+controller02 ansible_ssh_host=192.168.122.5
+
+[compute]
+compute00 ansible_ssh_host=192.168.122.6
+compute01 ansible_ssh_host=192.168.122.7
diff --git a/prototypes/openstack-ansible/scripts/osa-deploy.sh b/prototypes/openstack-ansible/scripts/osa-deploy.sh
new file mode 100755 (executable)
index 0000000..ec60744
--- /dev/null
@@ -0,0 +1,136 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o nounset
+set -o pipefail
+
+export OSA_PATH=/opt/openstack-ansible
+export LOG_PATH=$OSA_PATH/log
+export PLAYBOOK_PATH=$OSA_PATH/playbooks
+export OSA_BRANCH=${OSA_BRANCH:-"master"}
+XCIMASTER_IP="192.168.122.2"
+
+sudo /bin/rm -rf $LOG_PATH
+sudo /bin/mkdir -p $LOG_PATH
+sudo /bin/cp /root/.ssh/id_rsa.pub ../file/authorized_keys
+echo -e '\n' | sudo tee --append ../file/authorized_keys
+
+# log some info
+echo -e "\n"
+echo "***********************************************************************"
+echo "*                                                                     *"
+echo "*                        Configure XCI Master                         *"
+echo "*                                                                     *"
+echo "*  Bootstrap xci-master, configure network, clone openstack-ansible   *"
+echo "*                Playbooks: configure-xcimaster.yml                   *"
+echo "*                                                                     *"
+echo "***********************************************************************"
+echo -e "\n"
+
+cd ../playbooks/
+# this will prepare the jump host
+# git clone the Openstack-Ansible, bootstrap and configure network
+echo "xci: running ansible playbook configure-xcimaster.yml"
+sudo -E ansible-playbook -i inventory configure-xcimaster.yml
+
+echo "XCI Master is configured successfully!"
+
+# log some info
+echo -e "\n"
+echo "***********************************************************************"
+echo "*                                                                     *"
+echo "*                          Configure Nodes                            *"
+echo "*                                                                     *"
+echo "*       Configure network on OpenStack Nodes, configure NFS           *"
+echo "*                Playbooks: configure-targethosts.yml                 *"
+echo "*                                                                     *"
+echo "***********************************************************************"
+echo -e "\n"
+
+# this will prepare the target host
+# such as configure network and NFS
+echo "xci: running ansible playbook configure-targethosts.yml"
+sudo -E ansible-playbook -i inventory configure-targethosts.yml
+
+echo "Nodes are configured successfully!"
+
+# log some info
+echo -e "\n"
+echo "***********************************************************************"
+echo "*                                                                     *"
+echo "*                       Set Up OpenStack Nodes                        *"
+echo "*                                                                     *"
+echo "*            Set up OpenStack Nodes using openstack-ansible           *"
+echo "*         Playbooks: setup-hosts.yml, setup-infrastructure.yml        *"
+echo "*                                                                     *"
+echo "***********************************************************************"
+echo -e "\n"
+
+# using OpenStack-Ansible deploy the OpenStack
+echo "xci: running ansible playbook setup-hosts.yml"
+sudo -E /bin/sh -c "ssh root@$XCIMASTER_IP openstack-ansible \
+     $PLAYBOOK_PATH/setup-hosts.yml" | \
+     tee $LOG_PATH/setup-hosts.log
+
+# check the result of openstack-ansible setup-hosts.yml
+# if failed, exit with exit code 1
+if grep -q 'failed=1\|unreachable=1' $LOG_PATH/setup-hosts.log; then
+    echo "OpenStack node setup failed!"
+    exit 1
+fi
+
+echo "xci: running ansible playbook setup-infrastructure.yml"
+sudo -E /bin/sh -c "ssh root@$XCIMASTER_IP openstack-ansible \
+     $PLAYBOOK_PATH/setup-infrastructure.yml" | \
+     tee $LOG_PATH/setup-infrastructure.log
+
+# check the result of openstack-ansible setup-infrastructure.yml
+# if failed, exit with exit code 1
+if grep -q 'failed=1\|unreachable=1' $LOG_PATH/setup-infrastructure.log; then
+    echo "OpenStack node setup failed!"
+    exit 1
+fi
+
+echo "OpenStack nodes are setup successfully!"
+
+sudo -E /bin/sh -c "ssh root@$XCIMASTER_IP ansible -i $PLAYBOOK_PATH/inventory/ \
+           galera_container -m shell \
+           -a "mysql -h localhost -e 'show status like \"%wsrep_cluster_%\";'"" \
+           | tee $LOG_PATH/galera.log
+
+if grep -q 'FAILED' $LOG_PATH/galera.log; then
+    echo "Database cluster verification failed!"
+    exit 1
+else
+    echo "Database cluster verification successful!"
+fi
+
+# log some info
+echo -e "\n"
+echo "***********************************************************************"
+echo "*                                                                     *"
+echo "*                           Install OpenStack                         *"
+echo "*                 Playbooks: opnfv-setup-openstack.yml                *"
+echo "*                                                                     *"
+echo "***********************************************************************"
+echo -e "\n"
+
+echo "xci: running ansible playbook opnfv-setup-openstack.yml"
+sudo -E /bin/sh -c "ssh root@$XCIMASTER_IP openstack-ansible \
+     $PLAYBOOK_PATH/opnfv-setup-openstack.yml" | \
+     tee $LOG_PATH/opnfv-setup-openstack.log
+
+if grep -q 'failed=1\|unreachable=1' $LOG_PATH/opnfv-setup-openstack.log; then
+   echo "OpenStack installation failed!"
+   exit 1
+else
+   echo "OpenStack installation is successfully completed!"
+   exit 0
+fi
diff --git a/prototypes/openstack-ansible/template/bifrost/compute.interface.j2 b/prototypes/openstack-ansible/template/bifrost/compute.interface.j2
new file mode 100644 (file)
index 0000000..1719f6a
--- /dev/null
@@ -0,0 +1,86 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+
+# Physical interface
+auto ens3
+iface ens3 inet manual
+
+# Container/Host management VLAN interface
+auto ens3.10
+iface ens3.10 inet manual
+    vlan-raw-device ens3
+
+# OpenStack Networking VXLAN (tunnel/overlay) VLAN interface
+auto ens3.30
+iface ens3.30 inet manual
+    vlan-raw-device ens3
+
+# Storage network VLAN interface (optional)
+auto ens3.20
+iface ens3.20 inet manual
+    vlan-raw-device ens3
+
+# Container/Host management bridge
+auto br-mgmt
+iface br-mgmt inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3.10
+    address {{host_info[inventory_hostname].MGMT_IP}}
+    netmask 255.255.252.0
+
+# compute1 VXLAN (tunnel/overlay) bridge config
+auto br-vxlan
+iface br-vxlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3.30
+    address {{host_info[inventory_hostname].VXLAN_IP}}
+    netmask 255.255.252.0
+
+# OpenStack Networking VLAN bridge
+auto br-vlan
+iface br-vlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3
+    address {{host_info[inventory_hostname].VLAN_IP}}
+    netmask 255.255.255.0
+    gateway 192.168.122.1
+    offload-sg off
+    # Create veth pair, don't bomb if already exists
+    pre-up ip link add br-vlan-veth type veth peer name eth12 || true
+    # Set both ends UP
+    pre-up ip link set br-vlan-veth up
+    pre-up ip link set eth12 up
+    # Delete veth pair on DOWN
+    post-down ip link del br-vlan-veth || true
+    bridge_ports br-vlan-veth
+
+# Add an additional address to br-vlan
+iface br-vlan inet static
+    # Flat network default gateway
+    # -- This needs to exist somewhere for network reachability
+    # -- from the router namespace for floating IP paths.
+    # -- Putting this here is primarily for tempest to work.
+    address {{host_info[inventory_hostname].VLAN_IP_SECOND}}
+    netmask 255.255.252.0
+    dns-nameserver 8.8.8.8 8.8.4.4
+
+# compute1 Storage bridge
+auto br-storage
+iface br-storage inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3.20
+    address {{host_info[inventory_hostname].STORAGE_IP}}
+    netmask 255.255.252.0
diff --git a/prototypes/openstack-ansible/template/bifrost/controller.interface.j2 b/prototypes/openstack-ansible/template/bifrost/controller.interface.j2
new file mode 100644 (file)
index 0000000..74aeea9
--- /dev/null
@@ -0,0 +1,71 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+# Physical interface
+auto ens3
+iface ens3 inet manual
+
+# Container/Host management VLAN interface
+auto ens3.10
+iface ens3.10 inet manual
+    vlan-raw-device ens3
+
+# OpenStack Networking VXLAN (tunnel/overlay) VLAN interface
+auto ens3.30
+iface ens3.30 inet manual
+    vlan-raw-device ens3
+
+# Storage network VLAN interface (optional)
+auto ens3.20
+iface ens3.20 inet manual
+    vlan-raw-device ens3
+
+# Container/Host management bridge
+auto br-mgmt
+iface br-mgmt inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3.10
+    address {{host_info[inventory_hostname].MGMT_IP}}
+    netmask 255.255.252.0
+
+# OpenStack Networking VXLAN (tunnel/overlay) bridge
+#
+# Only the COMPUTE and NETWORK nodes must have an IP address
+# on this bridge. When used by infrastructure nodes, the
+# IP addresses are assigned to containers which use this
+# bridge.
+#
+auto br-vxlan
+iface br-vxlan inet manual
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3.30
+
+# OpenStack Networking VLAN bridge
+auto br-vlan
+iface br-vlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3
+    address {{host_info[inventory_hostname].VLAN_IP}}
+    netmask 255.255.255.0
+    gateway 192.168.122.1
+    dns-nameserver 8.8.8.8 8.8.4.4
+
+# compute1 Storage bridge
+auto br-storage
+iface br-storage inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports ens3.20
+    address {{host_info[inventory_hostname].STORAGE_IP}}
+    netmask 255.255.252.0
diff --git a/prototypes/openstack-ansible/var/ubuntu.yml b/prototypes/openstack-ansible/var/ubuntu.yml
new file mode 100644 (file)
index 0000000..eb595be
--- /dev/null
@@ -0,0 +1,8 @@
+---
+OSA_URL: https://git.openstack.org/openstack/openstack-ansible
+OSA_PATH: /opt/openstack-ansible
+OSA_ETC_PATH: /etc/openstack_deploy
+OPENSTACK_OSA_VERSION: "{{ lookup('env','OPENSTACK_OSA_VERSION') }}"
+
+XCIMASTER_IP: 192.168.122.2
+host_info: {'xcimaster':{'MGMT_IP': '172.29.236.10','VLAN_IP': '192.168.122.2', 'STORAGE_IP': '172.29.244.10'},'controller00':{'MGMT_IP': '172.29.236.11','VLAN_IP': '192.168.122.3', 'STORAGE_IP': '172.29.244.11'},'controller01':{'MGMT_IP': '172.29.236.12','VLAN_IP': '192.168.122.4', 'STORAGE_IP': '172.29.244.12'},'controller02':{'MGMT_IP': '172.29.236.13','VLAN_IP': '192.168.122.5', 'STORAGE_IP': '172.29.240.13'},'compute00':{'MGMT_IP': '172.29.236.14','VLAN_IP': '192.168.122.6','VLAN_IP_SECOND': '173.29.241.1','VXLAN_IP': '172.29.240.14', 'STORAGE_IP': '172.29.244.14'},'compute01':{'MGMT_IP': '172.29.236.15','VLAN_IP': '192.168.122.7','VLAN_IP_SECOND': '173.29.241.2','VXLAN_IP': '172.29.240.15', 'STORAGE_IP': '172.29.244.15'}}
index 334dff4..2bd0a53 100644 (file)
@@ -26,6 +26,7 @@ On the same bifrost VM, follow these steps:
 
  1. Source bifrost env vars: source /opt/stack/bifrost/env-vars
  2. Export baremetal servers inventory:  export BIFROST_INVENTORY-SOURCE=/opt/stack/baremetal.json 
+ 3. Change active directory: cd /opt/stack/bifrost/playbooks
  3. Enroll the servers: ansible-playbook -vvv -i inventory/bifrost_inventory.py enroll-dynamic.yaml -e @/etc/bifrost/bifrost_global_vars
  4. Deploy the servers:  ansible-playbook -vvv -i inventory/bifrost_inventory.py deploy-dynamic.yaml -e @/etc/bifrost/bifrost_global_vars
  5. Wait until they are on **active** state, check it with: ironic node-list
index 1943f66..634d96c 100644 (file)
@@ -1,3 +1,4 @@
+---
 keystone_rabbit_password: pass
 neutron_rabbit_password: pass
 nova_rabbit_password: pass
index 9825ed3..015612c 100644 (file)
@@ -1,3 +1,4 @@
+---
 keystone_rabbit_password: pass
 neutron_rabbit_password: pass
 nova_rabbit_password: pass
@@ -130,12 +131,12 @@ ironic_inventory:
     ipv4_address: 172.30.13.90
     ansible_ssh_host: 172.30.13.90
     ipv4_gateway: 172.30.13.1
-    ipv4_interface_mac: 00:1e:67:f9:9b:35
+    ipv4_interface_mac: 00:1e:67:f6:9b:35
     ipv4_subnet_mask: 255.255.255.192
     name: controller00.opnfvlocal
     nics:
-    - mac: a4:bf:01:01:a9:fc
-    - mac: 00:1e:67:f6:9b:35
+      - mac: a4:bf:01:01:a9:fc
+      - mac: 00:1e:67:f6:9b:35
     properties:
       cpu_arch: x86_64
       cpus: '44'
@@ -156,8 +157,8 @@ ironic_inventory:
     ipv4_subnet_mask: 255.255.255.0
     name: compute00.opnfvlocal
     nics:
-    - mac: a4:bf:01:01:a9:d4
-    - mac: 00:1e:67:f6:9b:37
+      - mac: a4:bf:01:01:a9:d4
+      - mac: 00:1e:67:f6:9b:37
     properties:
       cpu_arch: x86_64
       cpus: '44'
index 8cbfef8..3483b06 100644 (file)
@@ -10,13 +10,13 @@ node 'controller00.opnfvlocal' {
   $group = 'infracloud'
   include ::sudoers
 
-  class { 'opnfv::server':
+  class { '::opnfv::server':
     iptables_public_tcp_ports => [80,5000,5671,8774,9292,9696,35357], # logs,keystone,rabbit,nova,glance,neutron,keystone
     sysadmins                 => hiera('sysadmins', []),
     enable_unbound            => false,
     purge_apt_sources         => false,
   }
-  class { 'opnfv::controller':
+  class { '::opnfv::controller':
     keystone_rabbit_password         => hiera('keystone_rabbit_password'),
     neutron_rabbit_password          => hiera('neutron_rabbit_password'),
     nova_rabbit_password             => hiera('nova_rabbit_password'),
@@ -38,6 +38,7 @@ node 'controller00.opnfvlocal' {
     neutron_subnet_gateway           => hiera('neutron_subnet_gateway'),
     neutron_subnet_allocation_pools  => hiera('neutron_subnet_allocation_pools'),
     opnfv_password                   => hiera('opnfv_password'),
+    require                          => Class['::opnfv::server'],
   }
 }
 
@@ -45,13 +46,13 @@ node 'compute00.opnfvlocal' {
   $group = 'infracloud'
   include ::sudoers
 
-  class { 'opnfv::server':
+  class { '::opnfv::server':
     sysadmins                 => hiera('sysadmins', []),
     enable_unbound            => false,
     purge_apt_sources         => false,
   }
 
-  class { 'opnfv::compute':
+  class { '::opnfv::compute':
     nova_rabbit_password             => hiera('nova_rabbit_password'),
     neutron_rabbit_password          => hiera('neutron_rabbit_password'),
     neutron_admin_password           => hiera('neutron_admin_password'),
@@ -60,11 +61,12 @@ node 'compute00.opnfvlocal' {
     br_name                          => hiera('bridge_name'),
     controller_public_address        => 'controller00.opnfvlocal',
     virt_type                        => hiera('virt_type'),
+    require                          => Class['::opnfv::server'],
   }
 }
 
 node 'jumphost.opnfvlocal' {
-  class { 'opnfv::server':
+  class { '::opnfv::server':
     sysadmins                 => hiera('sysadmins', []),
     enable_unbound            => false,
     purge_apt_sources         => false,
@@ -97,5 +99,6 @@ node 'baremetal.opnfvlocal', 'lfpod5-jumpserver' {
     ipv4_subnet_mask          => hiera('ipv4_subnet_mask'),
     bridge_name               => hiera('bridge_name'),
     dib_dev_user_password     => hiera('dib_dev_user_password'),
+    require                   => Class['::opnfv::server'],
   }
 }
index 6b608a7..d167973 100644 (file)
@@ -22,7 +22,7 @@ class opnfv::server (
 
   class { 'iptables':
     public_tcp_ports => $iptables_public_tcp_ports,
-    public_udp_ports => $all_udp,
+    public_udp_ports => $iptables_public_udp_ports,
     rules4           => $iptables_rules4,
     rules6           => $iptables_rules6,
   }
@@ -239,13 +239,6 @@ class opnfv::server (
     multiple => true,
   }
 
-  # disable selinux in case of RHEL
-  if ($::osfamily == 'RedHat') {
-    class { 'selinux':
-      mode => 'disabled',
-    }
-  }
-
   # update hosts
   create_resources('host', hiera_hash('hosts'))
 }
diff --git a/prototypes/xci/README.rst b/prototypes/xci/README.rst
new file mode 100644 (file)
index 0000000..8318cdb
--- /dev/null
@@ -0,0 +1,217 @@
+###########################
+OPNFV XCI Developer Sandbox
+###########################
+
+The XCI Developer Sandbox is created by the OPNFV community for the OPNFV
+community in order to
+
+- provide means for OPNFV developers to work with OpenStack master branch,
+  cutting the time it takes to develop new features significantly and testing
+  them on OPNFV Infrastructure
+- enable OPNFV developers to identify bugs earlier, issue fixes faster, and
+  get feedback on a daily basis
+- establish mechanisms to run additional testing on OPNFV Infrastructure to
+  provide feedback to OpenStack community
+- make the solutions we put in place available to other LF Networking Projects
+  OPNFV works with closely
+
+More information about OPNFV XCI and the sandbox can be seen on
+`OPNFV Wiki <https://wiki.opnfv.org/pages/viewpage.action?pageId=8687635>`_.
+
+===================================
+Components of XCI Developer Sandbox
+===================================
+
+The sandbox uses OpenStack projects for VM node creation, provisioning
+and OpenStack installation.
+
+- **openstack/bifrost:** Bifrost (pronounced bye-frost) is a set of Ansible
+  playbooks that automates the task of deploying a base image onto a set
+  of known hardware using ironic. It provides modular utility for one-off
+  operating system deployment with as few operational requirements as
+  reasonably possible. Bifrost supports different operating systems such as
+  Ubuntu, CentOS, and openSUSE.
+  More information about this project can be seen on
+  `Bifrost documentation <https://docs.openstack.org/developer/bifrost/>`_.
+
+- **openstack/openstack-ansible:** OpenStack-Ansible is an official OpenStack
+  project which aims to deploy production environments from source in a way
+  that makes it scalable while also being simple to operate, upgrade, and grow.
+  More information about this project can be seen on
+  `OpenStack Ansible documentation <https://docs.openstack.org/developer/openstack-ansible/>`_.
+
+- **opnfv/releng:** OPNFV Releng Project provides additional scripts, Ansible
+  playbooks and configuration options in order for developers to have easy
+  way of using openstack/bifrost and openstack/openstack-ansible by just
+  setting couple of environment variables and executing a single script.
+  More infromation about this project can be seen on
+  `OPNFV Releng documentation <https://wiki.opnfv.org/display/releng>_`.
+
+==========
+Basic Flow
+==========
+
+Here are the steps that take place upon the execution of the sandbox script
+``xci-deploy.sh``:
+
+1. Sources environment variables in order to set things up properly.
+2. Installs ansible on the host where sandbox script is executed.
+3. Creates and provisions VM nodes based on the flavor chosen by the user.
+4. Configures the host where the sandbox script is executed.
+5. Configures the deployment host which the OpenStack installation will
+   be driven from.
+6. Configures the target hosts where OpenStack will be installed.
+7. Configures the target hosts as controller(s) and compute(s) nodes.
+8. Starts the OpenStack installation.
+
+=====================
+Sandbox Prerequisites
+=====================
+
+In order to use this sandbox, the host must have certain packages installed.
+
+- libvirt
+- python
+- pip
+- git
+- <fix the list with all the dependencies>
+- passwordless sudo
+
+The host must also have enough CPU/RAM/Disk in order to host number of VM
+nodes that will be created based on the chosen flavor. See the details from
+`this link <https://wiki.opnfv.org/display/INF/XCI+Developer+Sandbox#XCIDeveloperSandbox-Prerequisites>`_.
+
+===========================
+Flavors Provided by Sandbox
+===========================
+
+OPNFV XCI Sandbox provides different flavors such as all in one (aio) which
+puts much lower requirements on the host machine and full-blown HA.
+
+* aio: Single node which acts as the deployment host, controller and compute.
+* mini: One deployment host, 1 controller node and 1 compute node.
+* noha: One deployment host, 1 controller node and 2 compute nodes.
+* ha: One deployment host, 3 controller nodes and 2 compute nodes.
+
+See the details of the flavors from
+`this link <https://wiki.opnfv.org/display/INF/XCI+Developer+Sandbox#XCIDeveloperSandbox-AvailableFlavors>`_.
+
+==========
+How to Use
+==========
+
+Basic Usage
+-----------
+
+clone OPNFV Releng repository
+
+    git clone https://gerrit.opnfv.org/gerrit/releng.git
+
+change into directory where the sandbox script is located
+
+    cd releng/prototypes/xci
+
+execute sandbox script
+
+    sudo -E ./xci-deploy.sh
+
+Issuing above command will start aio sandbox deployment and the sandbox
+should be ready between 1,5 and 2 hours depending on the host machine.
+
+Advanced Usage
+--------------
+
+The flavor to deploy, the versions of upstream components to use can
+be configured by developers by setting certain environment variables.
+Below example deploys noha flavor using the latest of openstack-ansible
+master branch and stores logs in different location than what is configured.
+
+clone OPNFV Releng repository
+
+    git clone https://gerrit.opnfv.org/gerrit/releng.git
+
+change into directory where the sandbox script is located
+
+    cd releng/prototypes/xci
+
+set the sandbox flavor
+
+    export XCI_FLAVOR=noha
+
+set the version to use for openstack-ansible
+
+    export OPENSTACK_OSA_VERSION=master
+
+set where the logs should be stored
+
+    export LOG_PATH=/home/jenkins/xcilogs
+
+execute sandbox script
+
+    sudo -E ./xci-deploy.sh
+
+Warning::
+
+    Please encure you always execute the sandbox script using **sudo -E**
+    in order to make the environment variables you set available to the
+    sandbox script or you end up with the default settings.
+
+===============
+User Variables
+===============
+
+All user variables can be set from command line by exporting them before
+executing the script. The current user variables can be seen from
+``releng/prototypes/xci/config/user-vars``.
+
+The variables can also be set directly within the file before executing
+the sandbox script.
+
+===============
+Pinned Versions
+===============
+
+As explained above, the users can pick and choose which versions to use. If
+you want to be on the safe side, you can use the pinned versions the sandbox
+provides. They can be seen from ``releng/prototypes/xci/config/pinned-versions``.
+
+How Pinned Versions are Determined
+----------------------------------
+
+OPNFV runs periodic jobs against upstream projects openstack/bifrost and
+openstack/ansible using latest on master and stable/ocata branches,
+continuously chasing the HEAD of corresponding branches.
+
+Once a working version is identified, the versions of the upstream components
+are then bumped in releng repo.
+
+===========================================
+Limitations, Known Issues, and Improvements
+===========================================
+
+The list can be seen using `this link <https://jira.opnfv.org/issues/?filter=11616>`_.
+
+=========
+Changelog
+=========
+
+Changelog can be seen using `this link <https://jira.opnfv.org/issues/?filter=11625>`_.
+
+=======
+Testing
+=======
+
+Sandbox is continuously tested by OPNFV CI to ensure the changes do not impact
+users. In fact, OPNFV CI itself uses the sandbox scripts to run daily platform
+verification jobs.
+
+=======
+Support
+=======
+
+OPNFV XCI issues are tracked on OPNFV JIRA Releng project. If you encounter
+and issue or identify a bug, please submit an issue to JIRA using
+`this link <https://jira.opnfv.org/projects/RELENG>_`.
+
+If you have questions or comments, you can ask them on ``#opnfv-pharos`` IRC
+channel on Freenode.
diff --git a/prototypes/xci/config/aio-vars b/prototypes/xci/config/aio-vars
new file mode 100755 (executable)
index 0000000..f28ecff
--- /dev/null
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------------
+# XCI Flavor Configuration
+#-------------------------------------------------------------------------------
+# You are free to modify parts of the configuration to fit into your environment.
+# But before doing that, please ensure you checked other flavors to see if one
+# them can be used instead, saving you some time.
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+# Configure VM Nodes
+#-------------------------------------------------------------------------------
+export TEST_VM_NUM_NODES=1
+export TEST_VM_NODE_NAMES=opnfv
+export VM_DOMAIN_TYPE=kvm
+export VM_CPU=8
+export VM_DISK=80
+export VM_MEMORY_SIZE=8192
+export VM_DISK_CACHE=unsafe
diff --git a/prototypes/xci/config/env-vars b/prototypes/xci/config/env-vars
new file mode 100755 (executable)
index 0000000..cefb412
--- /dev/null
@@ -0,0 +1,22 @@
+#-------------------------------------------------------------------------------
+# !!! Changing or overriding these will most likely break everything altogether !!!
+#    Please do not change these settings if you are not developing for XCI!
+#-------------------------------------------------------------------------------
+export OPNFV_RELENG_GIT_URL=https://gerrit.opnfv.org/gerrit/releng.git
+export OPENSTACK_BIFROST_GIT_URL=https://git.openstack.org/openstack/bifrost
+export OPENSTACK_OSA_GIT_URL=https://git.openstack.org/openstack/openstack-ansible
+export OPENSTACK_OSA_ETC_PATH=/etc/openstack_deploy
+export CLEAN_DIB_IMAGES=false
+export OPNFV_HOST_IP=192.168.122.2
+export XCI_FLAVOR_ANSIBLE_FILE_PATH=$OPNFV_RELENG_PATH/prototypes/xci/file/$XCI_FLAVOR
+export CI_LOOP=${CI_LOOP:-daily}
+export JOB_NAME=${JOB_NAME:-false}
+# TODO: this currently matches to bifrost ansible version
+# there is perhaps better way to do this
+export XCI_ANSIBLE_PIP_VERSION=2.1.5.0
+export ANSIBLE_HOST_KEY_CHECKING=False
+export DISTRO=${DISTRO:-ubuntu}
+export DIB_OS_RELEASE=${DIB_OS_RELEASE:-xenial}
+export DIB_OS_ELEMENT=${DIB_OS_ELEMENT:-ubuntu-minimal}
+export DIB_OS_PACKAGES=${DIB_OS_PACKAGES:-"vlan,vim,less,bridge-utils,sudo,language-pack-en,iputils-ping,rsyslog,curl,python,debootstrap,ifenslave,ifenslave-2.6,lsof,lvm2,tcpdump,nfs-kernel-server,chrony,iptables"}
+export EXTRA_DIB_ELEMENTS=${EXTRA_DIB_ELEMENTS:-"openssh-server"}
diff --git a/prototypes/xci/config/ha-vars b/prototypes/xci/config/ha-vars
new file mode 100755 (executable)
index 0000000..1ba4589
--- /dev/null
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------------
+# XCI Flavor Configuration
+#-------------------------------------------------------------------------------
+# You are free to modify parts of the configuration to fit into your environment.
+# But before doing that, please ensure you checked other flavors to see if one
+# them can be used instead, saving you some time.
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+# Configure VM Nodes
+#-------------------------------------------------------------------------------
+export TEST_VM_NUM_NODES=6
+export TEST_VM_NODE_NAMES="opnfv controller00 controller01 controller02 compute00 compute01"
+export VM_DOMAIN_TYPE=kvm
+export VM_CPU=8
+export VM_DISK=80
+export VM_MEMORY_SIZE=16384
+export VM_DISK_CACHE=unsafe
diff --git a/prototypes/xci/config/mini-vars b/prototypes/xci/config/mini-vars
new file mode 100755 (executable)
index 0000000..8f1e83c
--- /dev/null
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------------
+# XCI Flavor Configuration
+#-------------------------------------------------------------------------------
+# You are free to modify parts of the configuration to fit into your environment.
+# But before doing that, please ensure you checked other flavors to see if one
+# them can be used instead, saving you some time.
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+# Configure VM Nodes
+#-------------------------------------------------------------------------------
+export TEST_VM_NUM_NODES=3
+export TEST_VM_NODE_NAMES="opnfv controller00 compute00"
+export VM_DOMAIN_TYPE=kvm
+export VM_CPU=8
+export VM_DISK=80
+export VM_MEMORY_SIZE=12288
+export VM_DISK_CACHE=unsafe
diff --git a/prototypes/xci/config/noha-vars b/prototypes/xci/config/noha-vars
new file mode 100755 (executable)
index 0000000..935becb
--- /dev/null
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------------
+# XCI Flavor Configuration
+#-------------------------------------------------------------------------------
+# You are free to modify parts of the configuration to fit into your environment.
+# But before doing that, please ensure you checked other flavors to see if one
+# them can be used instead, saving you some time.
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+# Configure VM Nodes
+#-------------------------------------------------------------------------------
+export TEST_VM_NUM_NODES=4
+export TEST_VM_NODE_NAMES="opnfv controller00 compute00 compute01"
+export VM_DOMAIN_TYPE=kvm
+export VM_CPU=8
+export VM_DISK=80
+export VM_MEMORY_SIZE=12288
+export VM_DISK_CACHE=unsafe
diff --git a/prototypes/xci/config/pinned-versions b/prototypes/xci/config/pinned-versions
new file mode 100755 (executable)
index 0000000..e3b49c7
--- /dev/null
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------------------------
+# Pinned Component Versions
+#-------------------------------------------------------------------------------
+# You are free to override these versions in user-vars to experiment with
+# different branches or with different commits but be aware that things might
+# not work as expected. You can set the versions you want to use before running
+# the main script on your shell as shown on the examples below.
+#
+# It is important to be consistent between branches you use for OpenStack
+# projects OPNFV XCI uses.
+#
+# Examples:
+#   export OPENSTACK_BIFROST_VERSION="stable/ocata"
+#   export OPENSTACK_OSA_VERSION="stable/ocata"
+# or
+#   export OPENSTACK_BIFROST_VERSION="master"
+#   export OPENSTACK_OSA_VERSION="master"
+# or
+#   export OPENSTACK_BIFROST_VERSION="a87f7ce6c8725b3bbffec7b2efa1e466796848a9"
+#   export OPENSTACK_OSA_VERSION="4713cf45e11b4ebca9fbed25d1389854602213d8"
+#-------------------------------------------------------------------------------
+# use releng from master until the development work with the sandbox is complete
+export OPNFV_RELENG_VERSION="master"
+# HEAD of "master" as of 04.04.2017
+export OPENSTACK_BIFROST_VERSION=${OPENSTACK_BIFROST_VERSION:-"6109f824e5510e794dbf1968c3859e8b6356d280"}
+# HEAD of "master" as of 04.04.2017
+export OPENSTACK_OSA_VERSION=${OPENSTACK_OSA_VERSION:-"d9e1330c7ff9d72a604b6b4f3af765f66a01b30e"}
diff --git a/prototypes/xci/config/user-vars b/prototypes/xci/config/user-vars
new file mode 100755 (executable)
index 0000000..d910405
--- /dev/null
@@ -0,0 +1,54 @@
+#-------------------------------------------------------------------------------
+# Set Deployment Flavor
+#-------------------------------------------------------------------------------
+# OPNFV XCI currently supports 4 different types of flavors:
+#   - all in one (aio): 1 opnfv VM which acts as controller and compute node
+#   - mini: 3 VMs, 1 opnfv VM deployment host, 1 controller, and 1 compute nodes
+#   - noha: 4 VMs, 1 opnfv VM deployment host, 1 controller, and 2 compute nodes
+#   - ha: 6 VMs, 1 opnfv VM deployment host, 3 controllers, and 2 compute nodes
+#
+# Apart from having different number of nodes, CPU, RAM, and disk allocations
+# also differ from each other. Please take a look at the env-vars files for
+# each of these flavors.
+#
+# Examples:
+#   export XCI_FLAVOR="aio"
+# or
+#   export XCI_FLAVOR="mini"
+# or
+#   export XCI_FLAVOR="noha"
+# or
+#   export XCI_FLAVOR="ha"
+#-------------------------------------------------------------------------------
+export XCI_FLAVOR=${XCI_FLAVOR:-aio}
+
+#-------------------------------------------------------------------------------
+# Set Paths to where git repositories of XCI Components will be cloned
+#-------------------------------------------------------------------------------
+# OPNFV XCI Sandbox is not verified to be used as non-root user as of yet so
+# changing these paths might break things.
+#-------------------------------------------------------------------------------
+export OPNFV_RELENG_PATH=/opt/releng
+export OPENSTACK_BIFROST_PATH=/opt/bifrost
+export OPENSTACK_OSA_PATH=/opt/openstack-ansible
+
+#-------------------------------------------------------------------------------
+# Set the playbook to use for OpenStack deployment
+#-------------------------------------------------------------------------------
+# The variable can be overriden in order to install additional OpenStack services
+# supported by OpenStack Ansible or exclude certain OpenStack services.
+#-------------------------------------------------------------------------------
+export OPNFV_OSA_PLAYBOOK=${OPNFV_OSA_PLAYBOOK:-"$OPENSTACK_OSA_PATH/playbooks/setup-openstack.yml"}
+
+#-------------------------------------------------------------------------------
+# Configure some other stuff
+#-------------------------------------------------------------------------------
+# Set the verbosity for ansible
+#
+# Examples:
+#   ANSIBLE_VERBOSITY="-v"
+# or
+#   ANSIBLE_VERBOSITY="-vvvv"
+export ANSIBLE_VERBOSITY=${ANSIBLE_VERBOSITY-""}
+export LOG_PATH=${LOG_PATH:-/opt/opnfv/logs}
+export RUN_TEMPEST=${RUN_TEMPEST:-false}
diff --git a/prototypes/xci/docs/developer-guide.rst b/prototypes/xci/docs/developer-guide.rst
new file mode 100644 (file)
index 0000000..9a07b12
--- /dev/null
@@ -0,0 +1,31 @@
+#########################
+OPNFV XCI Developer Guide
+#########################
+
+This document will contain details about the XCI and how things are put
+together in order to support different flavors and different distros in future.
+
+Document is for anyone who will
+
+- do hands on development with XCI such as new features to XCI itself or
+  bugfixes
+- integrate new features
+- want to know what is going on behind the scenes
+
+It will also have guidance regarding how to develop for the sandbox.
+
+If you are looking for User's Guide, please check README.rst in the root of
+xci folder or take a look at
+`Wiki <https://wiki.opnfv.org/display/INF/OpenStack>`_.
+
+===================================
+Components of XCI Developer Sandbox
+===================================
+
+TBD
+
+=============
+Detailed Flow
+=============
+
+TBD
diff --git a/prototypes/xci/file/aio/configure-opnfvhost.yml b/prototypes/xci/file/aio/configure-opnfvhost.yml
new file mode 100644 (file)
index 0000000..5c66d40
--- /dev/null
@@ -0,0 +1,22 @@
+---
+- hosts: opnfv
+  remote_user: root
+  vars_files:
+  vars_files:
+    - ../var/opnfv.yml
+  roles:
+    - role: remove-folders
+    - { role: clone-repository, project: "openstack/openstack-ansible", repo: "{{ OPENSTACK_OSA_GIT_URL }}", dest: "{{ OPENSTACK_OSA_PATH }}", version: "{{ OPENSTACK_OSA_VERSION }}" }
+  tasks:
+    - name: bootstrap ansible on opnfv host
+      command: "/bin/bash ./scripts/bootstrap-ansible.sh"
+      args:
+        chdir: "{{OPENSTACK_OSA_PATH}}"
+    - name: bootstrap opnfv host as aio
+      command: "/bin/bash ./scripts/bootstrap-aio.sh"
+      args:
+        chdir: "{{OPENSTACK_OSA_PATH}}"
+    - name: install OpenStack on opnfv host - this command doesn't log anything to console
+      command: "/bin/bash ./scripts/run-playbooks.sh"
+      args:
+        chdir: "{{OPENSTACK_OSA_PATH}}"
diff --git a/prototypes/xci/file/aio/flavor-vars.yml b/prototypes/xci/file/aio/flavor-vars.yml
new file mode 100644 (file)
index 0000000..6ac1e0f
--- /dev/null
@@ -0,0 +1,3 @@
+---
+# this file is added intentionally in order to simplify putting files in place
+# in future, it might contain vars specific to this flavor
diff --git a/prototypes/xci/file/aio/inventory b/prototypes/xci/file/aio/inventory
new file mode 100644 (file)
index 0000000..9a3dd9e
--- /dev/null
@@ -0,0 +1,2 @@
+[opnfv]
+opnfv ansible_ssh_host=192.168.122.2
diff --git a/prototypes/xci/file/ansible-role-requirements.yml b/prototypes/xci/file/ansible-role-requirements.yml
new file mode 100644 (file)
index 0000000..842bcc4
--- /dev/null
@@ -0,0 +1,199 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+# these versions are extracted based on the osa commit d9e1330c7ff9d72a604b6b4f3af765f66a01b30e on 04.04.2017
+# https://review.openstack.org/gitweb?p=openstack/openstack-ansible.git;a=commit;h=d9e1330c7ff9d72a604b6b4f3af765f66a01b30e
+- name: apt_package_pinning
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-apt_package_pinning
+  version: 364fc9fcd8ff652546c13d9c20ac808bc0e35f66
+- name: pip_install
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-pip_install
+  version: 793ae4d01397bd91ebe18e9670e8e27d1ae91960
+- name: galera_client
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-galera_client
+  version: c093c13e01826da545bf9a0259e0be441bc1b5e1
+- name: galera_server
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-galera_server
+  version: fd0a6b104a32badbe7e7594e2c829261a53bfb11
+- name: ceph_client
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-ceph_client
+  version: 9149bfa8e3c4284b656834ba7765ea3aa48bec2e
+- name: haproxy_server
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-haproxy_server
+  version: 32415ab81c61083ac5a83b65274703e4a5470e5e
+- name: keepalived
+  scm: git
+  src: https://github.com/evrardjp/ansible-keepalived
+  version: 4f7c8eb16e3cbd8c8748f126c1eea73db5c8efe9
+- name: lxc_container_create
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-lxc_container_create
+  version: 097da38126d90cfca36cdc3955aaf658a00db599
+- name: lxc_hosts
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-lxc_hosts
+  version: 2931d0c87a1c592ad7f1f2f83cdcf468e8dea932
+- name: memcached_server
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-memcached_server
+  version: 58e17aa13ebe7b0aa5da7c00afc75d6716d2720d
+- name: openstack-ansible-security
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-security
+  version: 9d745ec4fe8ac3e6d6cbb2412abe5196a9d2dad7
+- name: openstack_hosts
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-openstack_hosts
+  version: 2076dfddf418b1bdd64d3782346823902aa996bc
+- name: os_keystone
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_keystone
+  version: cee7a02143a1826479e6444c6fb5f1c2b6074ab7
+- name: openstack_openrc
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-openstack_openrc
+  version: fb98ad8d7bfe7fba0c964cb061313f1b8767c4b0
+- name: os_aodh
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_aodh
+  version: 9dcacb8fd6feef02e485f99c83535707ae67876b
+- name: os_barbican
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_barbican
+  version: bb3f39cb2f3c31c6980aa65c8953ff6293b992c0
+- name: os_ceilometer
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_ceilometer
+  version: 178ad8245fa019f0610c628c58c377997b011e8a
+- name: os_cinder
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_cinder
+  version: 1321fd39d8f55d1dc3baf91b4194469b349d7dc4
+- name: os_glance
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_glance
+  version: f39ef212bfa2edff8334bfb632cc463001c77c11
+- name: os_gnocchi
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_gnocchi
+  version: 318bd76e5e72402e8ff5b372b469c27a9395341b
+- name: os_heat
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_heat
+  version: 07d59ddb757b2d2557fba52ac537803e646e65b4
+- name: os_horizon
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_horizon
+  version: 69ef49c4f7a42f082f4bcff824d13f57145e2b83
+- name: os_ironic
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_ironic
+  version: 57e8a0eaaa2159f33e64a1b037180383196919d1
+- name: os_magnum
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_magnum
+  version: 8329c257dff25686827bd1cc904506d76ad1d12f
+- name: os_trove
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_trove
+  version: b948402c76d6188caa7be376098354cdb850d638
+- name: os_neutron
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_neutron
+  version: 2a92a4e1857e7457683aefd87ee5a4e751fc701a
+- name: os_nova
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_nova
+  version: 511963b7921ec7c2db24e8ee1d71a940b0aafae4
+- name: os_rally
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_rally
+  version: 96153c5b3285d11d00611a03135c9d8f267e0f52
+- name: os_sahara
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_sahara
+  version: 012d3f3530f878e5143d58380f94d1f514baad04
+- name: os_swift
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_swift
+  version: d62d6a23ac0b01d0320dbcb6c710dfd5f3cecfdf
+- name: os_tempest
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_tempest
+  version: 9d2bfb09d1ebbc9102329b0d42de33aa321e57b1
+- name: plugins
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-plugins
+  version: 3d2e23bb7e1d6775789d7f65ce8a878a7ee1d3c7
+- name: rabbitmq_server
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-rabbitmq_server
+  version: 9b0ce64fe235705e237bc4b476ecc0ad602d67a8
+- name: repo_build
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-repo_build
+  version: fe3ae20f74a912925d5c78040984957a6d55f9de
+- name: repo_server
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-repo_server
+  version: 7ea0820e0941282cd5c5cc263e939ffbee54ba52
+- name: rsyslog_client
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-rsyslog_client
+  version: 19615e47137eee46ee92c0308532fe1d2212333c
+- name: rsyslog_server
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-rsyslog_server
+  version: efd7b21798da49802012e390a0ddf7cc38636eeb
+- name: sshd
+  scm: git
+  src: https://github.com/willshersystems/ansible-sshd
+  version: 426e11c4dffeca09fcc4d16103a91e5e65180040
+- name: bird
+  scm: git
+  src: https://github.com/logan2211/ansible-bird
+  version: 2c4d29560d3617abddf0e63e0c95536364dedd92
+- name: etcd
+  scm: git
+  src: https://github.com/logan2211/ansible-etcd
+  version: ef63b0c5fd352b61084fd5aca286ee7f3fea932b
+- name: unbound
+  scm: git
+  src: https://github.com/logan2211/ansible-unbound
+  version: 5329d03eb9c15373d648a801563087c576bbfcde
+- name: resolvconf
+  scm: git
+  src: https://github.com/logan2211/ansible-resolvconf
+  version: 3b2b7cf2e900b194829565b351bf32bb63954548
+- name: os_designate
+  scm: git
+  src: https://git.openstack.org/openstack/openstack-ansible-os_designate
+  version: b7098a6bdea73c869f45a86e0cc78d21b032161e
+- name: ceph.ceph-common
+  scm: git
+  src: https://github.com/ceph/ansible-ceph-common
+  version: ef149767fa9565ec887f0bdb007ff752bd61e5d5
+- name: ceph.ceph-docker-common
+  scm: git
+  src: https://github.com/ceph/ansible-ceph-docker-common
+  version: ca86fd0ef6d24aa2c750a625acdcb8012c374aa0
+- name: ceph-mon
+  scm: git
+  src: https://github.com/ceph/ansible-ceph-mon
+  version: c5be4d6056dfe6a482ca3fcc483a6050cc8929a1
+- name: ceph-osd
+  scm: git
+  src: https://github.com/ceph/ansible-ceph-osd
+  version: 7bc5a61ceb96e487b7a9fe9643f6dafa6492f2b5
diff --git a/prototypes/xci/file/cinder.yml b/prototypes/xci/file/cinder.yml
new file mode 100644 (file)
index 0000000..e40b392
--- /dev/null
@@ -0,0 +1,13 @@
+---
+# This file contains an example to show how to set
+# the cinder-volume service to run in a container.
+#
+# Important note:
+# When using LVM or any iSCSI-based cinder backends, such as NetApp with
+# iSCSI protocol, the cinder-volume service *must* run on metal.
+# Reference: https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/1226855
+
+container_skel:
+  cinder_volumes_container:
+    properties:
+      is_metal: false
diff --git a/prototypes/xci/file/exports b/prototypes/xci/file/exports
new file mode 100644 (file)
index 0000000..af64d61
--- /dev/null
@@ -0,0 +1,14 @@
+# /etc/exports: the access control list for filesystems which may be exported
+#               to NFS clients.  See exports(5).
+#
+# Example for NFSv2 and NFSv3:
+# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
+#
+# Example for NFSv4:
+# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
+# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
+#
+# glance images are stored on compute host and made available to image hosts via nfs
+# see image_hosts section in openstack_user_config.yml for details
+/images         *(rw,sync,no_subtree_check,no_root_squash)
+
diff --git a/prototypes/xci/file/ha/configure-targethosts.yml b/prototypes/xci/file/ha/configure-targethosts.yml
new file mode 100644 (file)
index 0000000..6dc147f
--- /dev/null
@@ -0,0 +1,36 @@
+---
+- hosts: all
+  remote_user: root
+  tasks:
+    - name: add public key to host
+      copy:
+        src: ../file/authorized_keys
+        dest: /root/.ssh/authorized_keys
+    - name: configure modules
+      copy:
+        src: ../file/modules
+        dest: /etc/modules
+
+- hosts: controller
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+  roles:
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/controller.interface.j2", dest: "/etc/network/interfaces" }
+
+- hosts: compute
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+  roles:
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/compute.interface.j2", dest: "/etc/network/interfaces" }
+
+- hosts: compute01
+  remote_user: root
+  # TODO: this role is for configuring NFS on xenial and adjustment needed for other distros
+  roles:
+    - role: configure-nfs
diff --git a/prototypes/xci/file/ha/flavor-vars.yml b/prototypes/xci/file/ha/flavor-vars.yml
new file mode 100644 (file)
index 0000000..3cd1d62
--- /dev/null
@@ -0,0 +1,37 @@
+---
+host_info: {
+    'opnfv': {
+        'MGMT_IP': '172.29.236.10',
+        'VLAN_IP': '192.168.122.2',
+        'STORAGE_IP': '172.29.244.10'
+    },
+    'controller00': {
+        'MGMT_IP': '172.29.236.11',
+        'VLAN_IP': '192.168.122.3',
+        'STORAGE_IP': '172.29.244.11'
+    },
+    'controller01': {
+        'MGMT_IP': '172.29.236.12',
+        'VLAN_IP': '192.168.122.4',
+        'STORAGE_IP': '172.29.244.12'
+    },
+    'controller02': {
+        'MGMT_IP': '172.29.236.13',
+        'VLAN_IP': '192.168.122.5',
+        'STORAGE_IP': '172.29.244.13'
+    },
+    'compute00': {
+        'MGMT_IP': '172.29.236.14',
+        'VLAN_IP': '192.168.122.6',
+        'STORAGE_IP': '172.29.244.14',
+        'VLAN_IP_SECOND': '173.29.241.1',
+        'VXLAN_IP': '172.29.240.14'
+    },
+    'compute01': {
+        'MGMT_IP': '172.29.236.15',
+        'VLAN_IP': '192.168.122.7',
+        'STORAGE_IP': '172.29.244.15',
+        'VLAN_IP_SECOND': '173.29.241.2',
+        'VXLAN_IP': '172.29.240.15'
+    }
+}
diff --git a/prototypes/xci/file/ha/inventory b/prototypes/xci/file/ha/inventory
new file mode 100644 (file)
index 0000000..94b1d07
--- /dev/null
@@ -0,0 +1,11 @@
+[opnfv]
+opnfv ansible_ssh_host=192.168.122.2
+
+[controller]
+controller00 ansible_ssh_host=192.168.122.3
+controller01 ansible_ssh_host=192.168.122.4
+controller02 ansible_ssh_host=192.168.122.5
+
+[compute]
+compute00 ansible_ssh_host=192.168.122.6
+compute01 ansible_ssh_host=192.168.122.7
diff --git a/prototypes/xci/file/ha/openstack_user_config.yml b/prototypes/xci/file/ha/openstack_user_config.yml
new file mode 100644 (file)
index 0000000..0c43702
--- /dev/null
@@ -0,0 +1,245 @@
+---
+cidr_networks:
+  container: 172.29.236.0/22
+  tunnel: 172.29.240.0/22
+  storage: 172.29.244.0/22
+
+used_ips:
+  - "172.29.236.1,172.29.236.50"
+  - "172.29.240.1,172.29.240.50"
+  - "172.29.244.1,172.29.244.50"
+  - "172.29.248.1,172.29.248.50"
+
+global_overrides:
+  internal_lb_vip_address: 172.29.236.222
+  external_lb_vip_address: 192.168.122.220
+  tunnel_bridge: "br-vxlan"
+  management_bridge: "br-mgmt"
+  provider_networks:
+    - network:
+        container_bridge: "br-mgmt"
+        container_type: "veth"
+        container_interface: "eth1"
+        ip_from_q: "container"
+        type: "raw"
+        group_binds:
+          - all_containers
+          - hosts
+        is_container_address: true
+        is_ssh_address: true
+    - network:
+        container_bridge: "br-vxlan"
+        container_type: "veth"
+        container_interface: "eth10"
+        ip_from_q: "tunnel"
+        type: "vxlan"
+        range: "1:1000"
+        net_name: "vxlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth12"
+        host_bind_override: "eth12"
+        type: "flat"
+        net_name: "flat"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth11"
+        type: "vlan"
+        range: "1:1"
+        net_name: "vlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-storage"
+        container_type: "veth"
+        container_interface: "eth2"
+        ip_from_q: "storage"
+        type: "raw"
+        group_binds:
+          - glance_api
+          - cinder_api
+          - cinder_volume
+          - nova_compute
+
+# ##
+# ## Infrastructure
+# ##
+
+# galera, memcache, rabbitmq, utility
+shared-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# repository (apt cache, python packages, etc)
+repo-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# load balancer
+# Ideally the load balancer should not use the Infrastructure hosts.
+# Dedicated hardware is best for improved performance and security.
+haproxy_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# rsyslog server
+# log_hosts:
+# log1:
+#  ip: 172.29.236.14
+
+# ##
+# ## OpenStack
+# ##
+
+# keystone
+identity_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# cinder api services
+storage-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# glance
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+image_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.15"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+  controller01:
+    ip: 172.29.236.12
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.15"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+  controller02:
+    ip: 172.29.236.13
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.15"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+
+# nova api, conductor, etc services
+compute-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# heat
+orchestration_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# horizon
+dashboard_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# neutron server, agents (L3, etc)
+network_hosts:
+  controller00:
+    ip: 172.29.236.11
+  controller01:
+    ip: 172.29.236.12
+  controller02:
+    ip: 172.29.236.13
+
+# nova hypervisors
+compute_hosts:
+  compute00:
+    ip: 172.29.236.14
+  compute01:
+    ip: 172.29.236.15
+
+# cinder volume hosts (NFS-backed)
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+storage_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.11"
+  controller01:
+    ip: 172.29.236.12
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.12"
+  controller02:
+    ip: 172.29.236.13
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.13"
diff --git a/prototypes/xci/file/ha/user_variables.yml b/prototypes/xci/file/ha/user_variables.yml
new file mode 100644 (file)
index 0000000..094cc8c
--- /dev/null
@@ -0,0 +1,28 @@
+---
+# Copyright 2014, Rackspace US, Inc.
+#
+# 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.
+
+# ##
+# ## This file contains commonly used overrides for convenience. Please inspect
+# ## the defaults for each role to find additional override options.
+# ##
+
+# # Debug and Verbose options.
+debug: false
+
+haproxy_keepalived_external_vip_cidr: "192.168.122.220/32"
+haproxy_keepalived_internal_vip_cidr: "172.29.236.222/32"
+haproxy_keepalived_external_interface: br-vlan
+haproxy_keepalived_internal_interface: br-mgmt
+gnocchi_db_sync_options: ""
diff --git a/prototypes/xci/file/mini/configure-targethosts.yml b/prototypes/xci/file/mini/configure-targethosts.yml
new file mode 100644 (file)
index 0000000..395f44a
--- /dev/null
@@ -0,0 +1,32 @@
+---
+- hosts: all
+  remote_user: root
+  tasks:
+    - name: add public key to host
+      copy:
+        src: ../file/authorized_keys
+        dest: /root/.ssh/authorized_keys
+    - name: configure modules
+      copy:
+        src: ../file/modules
+        dest: /etc/modules
+
+- hosts: controller
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+  roles:
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/controller.interface.j2", dest: "/etc/network/interfaces" }
+
+- hosts: compute
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+  roles:
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/compute.interface.j2", dest: "/etc/network/interfaces" }
+  # TODO: this role is for configuring NFS on xenial and adjustment needed for other distros
+    - role: configure-nfs
diff --git a/prototypes/xci/file/mini/flavor-vars.yml b/prototypes/xci/file/mini/flavor-vars.yml
new file mode 100644 (file)
index 0000000..01fba71
--- /dev/null
@@ -0,0 +1,20 @@
+---
+host_info: {
+    'opnfv': {
+        'MGMT_IP': '172.29.236.10',
+        'VLAN_IP': '192.168.122.2',
+        'STORAGE_IP': '172.29.244.10'
+    },
+    'controller00': {
+        'MGMT_IP': '172.29.236.11',
+        'VLAN_IP': '192.168.122.3',
+        'STORAGE_IP': '172.29.244.11'
+    },
+    'compute00': {
+        'MGMT_IP': '172.29.236.12',
+        'VLAN_IP': '192.168.122.4',
+        'VLAN_IP_SECOND': '173.29.241.1',
+        'VXLAN_IP': '172.29.240.12',
+        'STORAGE_IP': '172.29.244.12'
+    },
+}
diff --git a/prototypes/xci/file/mini/inventory b/prototypes/xci/file/mini/inventory
new file mode 100644 (file)
index 0000000..eb73e5e
--- /dev/null
@@ -0,0 +1,8 @@
+[opnfv]
+opnfv ansible_ssh_host=192.168.122.2
+
+[controller]
+controller00 ansible_ssh_host=192.168.122.3
+
+[compute]
+compute00 ansible_ssh_host=192.168.122.4
diff --git a/prototypes/xci/file/mini/openstack_user_config.yml b/prototypes/xci/file/mini/openstack_user_config.yml
new file mode 100644 (file)
index 0000000..70429ce
--- /dev/null
@@ -0,0 +1,167 @@
+---
+cidr_networks:
+  container: 172.29.236.0/22
+  tunnel: 172.29.240.0/22
+  storage: 172.29.244.0/22
+
+used_ips:
+  - "172.29.236.1,172.29.236.50"
+  - "172.29.240.1,172.29.240.50"
+  - "172.29.244.1,172.29.244.50"
+  - "172.29.248.1,172.29.248.50"
+
+global_overrides:
+  internal_lb_vip_address: 172.29.236.11
+  external_lb_vip_address: 192.168.122.3
+  tunnel_bridge: "br-vxlan"
+  management_bridge: "br-mgmt"
+  provider_networks:
+    - network:
+        container_bridge: "br-mgmt"
+        container_type: "veth"
+        container_interface: "eth1"
+        ip_from_q: "container"
+        type: "raw"
+        group_binds:
+          - all_containers
+          - hosts
+        is_container_address: true
+        is_ssh_address: true
+    - network:
+        container_bridge: "br-vxlan"
+        container_type: "veth"
+        container_interface: "eth10"
+        ip_from_q: "tunnel"
+        type: "vxlan"
+        range: "1:1000"
+        net_name: "vxlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth12"
+        host_bind_override: "eth12"
+        type: "flat"
+        net_name: "flat"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth11"
+        type: "vlan"
+        range: "1:1"
+        net_name: "vlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-storage"
+        container_type: "veth"
+        container_interface: "eth2"
+        ip_from_q: "storage"
+        type: "raw"
+        group_binds:
+          - glance_api
+          - cinder_api
+          - cinder_volume
+          - nova_compute
+
+# ##
+# ## Infrastructure
+# ##
+
+# galera, memcache, rabbitmq, utility
+shared-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# repository (apt cache, python packages, etc)
+repo-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# load balancer
+# Ideally the load balancer should not use the Infrastructure hosts.
+# Dedicated hardware is best for improved performance and security.
+haproxy_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# rsyslog server
+# log_hosts:
+# log1:
+#  ip: 172.29.236.14
+
+# ##
+# ## OpenStack
+# ##
+
+# keystone
+identity_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# cinder api services
+storage-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# glance
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+image_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.12"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+
+# nova api, conductor, etc services
+compute-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# heat
+orchestration_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# horizon
+dashboard_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# neutron server, agents (L3, etc)
+network_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# nova hypervisors
+compute_hosts:
+  compute00:
+    ip: 172.29.236.12
+
+# cinder volume hosts (NFS-backed)
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+storage_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.11"
diff --git a/prototypes/xci/file/mini/user_variables.yml b/prototypes/xci/file/mini/user_variables.yml
new file mode 100644 (file)
index 0000000..7a0b806
--- /dev/null
@@ -0,0 +1,28 @@
+---
+# Copyright 2014, Rackspace US, Inc.
+#
+# 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.
+
+# ##
+# ## This file contains commonly used overrides for convenience. Please inspect
+# ## the defaults for each role to find additional override options.
+# ##
+
+# # Debug and Verbose options.
+debug: false
+
+haproxy_keepalived_external_vip_cidr: "192.168.122.3/32"
+haproxy_keepalived_internal_vip_cidr: "172.29.236.11/32"
+haproxy_keepalived_external_interface: br-vlan
+haproxy_keepalived_internal_interface: br-mgmt
+gnocchi_db_sync_options: ""
diff --git a/prototypes/xci/file/modules b/prototypes/xci/file/modules
new file mode 100644 (file)
index 0000000..60a517f
--- /dev/null
@@ -0,0 +1,8 @@
+# /etc/modules: kernel modules to load at boot time.
+#
+# This file contains the names of kernel modules that should be loaded
+# at boot time, one per line. Lines beginning with "#" are ignored.
+# Parameters can be specified after the module name.
+
+bonding
+8021q
diff --git a/prototypes/xci/file/noha/configure-targethosts.yml b/prototypes/xci/file/noha/configure-targethosts.yml
new file mode 100644 (file)
index 0000000..6dc147f
--- /dev/null
@@ -0,0 +1,36 @@
+---
+- hosts: all
+  remote_user: root
+  tasks:
+    - name: add public key to host
+      copy:
+        src: ../file/authorized_keys
+        dest: /root/.ssh/authorized_keys
+    - name: configure modules
+      copy:
+        src: ../file/modules
+        dest: /etc/modules
+
+- hosts: controller
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+  roles:
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/controller.interface.j2", dest: "/etc/network/interfaces" }
+
+- hosts: compute
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+  roles:
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/compute.interface.j2", dest: "/etc/network/interfaces" }
+
+- hosts: compute01
+  remote_user: root
+  # TODO: this role is for configuring NFS on xenial and adjustment needed for other distros
+  roles:
+    - role: configure-nfs
diff --git a/prototypes/xci/file/noha/flavor-vars.yml b/prototypes/xci/file/noha/flavor-vars.yml
new file mode 100644 (file)
index 0000000..7f52d34
--- /dev/null
@@ -0,0 +1,27 @@
+---
+host_info: {
+    'opnfv': {
+        'MGMT_IP': '172.29.236.10',
+        'VLAN_IP': '192.168.122.2',
+        'STORAGE_IP': '172.29.244.10'
+    },
+    'controller00': {
+        'MGMT_IP': '172.29.236.11',
+        'VLAN_IP': '192.168.122.3',
+        'STORAGE_IP': '172.29.244.11'
+    },
+    'compute00': {
+        'MGMT_IP': '172.29.236.12',
+        'VLAN_IP': '192.168.122.4',
+        'VLAN_IP_SECOND': '173.29.241.1',
+        'VXLAN_IP': '172.29.240.12',
+        'STORAGE_IP': '172.29.244.12'
+    },
+    'compute01': {
+        'MGMT_IP': '172.29.236.13',
+        'VLAN_IP': '192.168.122.5',
+        'VLAN_IP_SECOND': '173.29.241.2',
+        'VXLAN_IP': '172.29.240.13',
+        'STORAGE_IP': '172.29.244.13'
+    }
+}
diff --git a/prototypes/xci/file/noha/inventory b/prototypes/xci/file/noha/inventory
new file mode 100644 (file)
index 0000000..b4f9f6d
--- /dev/null
@@ -0,0 +1,9 @@
+[opnfv]
+opnfv ansible_ssh_host=192.168.122.2
+
+[controller]
+controller00 ansible_ssh_host=192.168.122.3
+
+[compute]
+compute00 ansible_ssh_host=192.168.122.4
+compute01 ansible_ssh_host=192.168.122.5
diff --git a/prototypes/xci/file/noha/openstack_user_config.yml b/prototypes/xci/file/noha/openstack_user_config.yml
new file mode 100644 (file)
index 0000000..05de6a9
--- /dev/null
@@ -0,0 +1,169 @@
+---
+cidr_networks:
+  container: 172.29.236.0/22
+  tunnel: 172.29.240.0/22
+  storage: 172.29.244.0/22
+
+used_ips:
+  - "172.29.236.1,172.29.236.50"
+  - "172.29.240.1,172.29.240.50"
+  - "172.29.244.1,172.29.244.50"
+  - "172.29.248.1,172.29.248.50"
+
+global_overrides:
+  internal_lb_vip_address: 172.29.236.11
+  external_lb_vip_address: 192.168.122.3
+  tunnel_bridge: "br-vxlan"
+  management_bridge: "br-mgmt"
+  provider_networks:
+    - network:
+        container_bridge: "br-mgmt"
+        container_type: "veth"
+        container_interface: "eth1"
+        ip_from_q: "container"
+        type: "raw"
+        group_binds:
+          - all_containers
+          - hosts
+        is_container_address: true
+        is_ssh_address: true
+    - network:
+        container_bridge: "br-vxlan"
+        container_type: "veth"
+        container_interface: "eth10"
+        ip_from_q: "tunnel"
+        type: "vxlan"
+        range: "1:1000"
+        net_name: "vxlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth12"
+        host_bind_override: "eth12"
+        type: "flat"
+        net_name: "flat"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-vlan"
+        container_type: "veth"
+        container_interface: "eth11"
+        type: "vlan"
+        range: "1:1"
+        net_name: "vlan"
+        group_binds:
+          - neutron_linuxbridge_agent
+    - network:
+        container_bridge: "br-storage"
+        container_type: "veth"
+        container_interface: "eth2"
+        ip_from_q: "storage"
+        type: "raw"
+        group_binds:
+          - glance_api
+          - cinder_api
+          - cinder_volume
+          - nova_compute
+
+# ##
+# ## Infrastructure
+# ##
+
+# galera, memcache, rabbitmq, utility
+shared-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# repository (apt cache, python packages, etc)
+repo-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# load balancer
+# Ideally the load balancer should not use the Infrastructure hosts.
+# Dedicated hardware is best for improved performance and security.
+haproxy_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# rsyslog server
+# log_hosts:
+# log1:
+#  ip: 172.29.236.14
+
+# ##
+# ## OpenStack
+# ##
+
+# keystone
+identity_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# cinder api services
+storage-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# glance
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+image_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      limit_container_types: glance
+      glance_nfs_client:
+        - server: "172.29.244.13"
+          remote_path: "/images"
+          local_path: "/var/lib/glance/images"
+          type: "nfs"
+          options: "_netdev,auto"
+
+# nova api, conductor, etc services
+compute-infra_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# heat
+orchestration_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# horizon
+dashboard_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# neutron server, agents (L3, etc)
+network_hosts:
+  controller00:
+    ip: 172.29.236.11
+
+# nova hypervisors
+compute_hosts:
+  compute00:
+    ip: 172.29.236.12
+  compute01:
+    ip: 172.29.236.13
+
+# cinder volume hosts (NFS-backed)
+# The settings here are repeated for each infra host.
+# They could instead be applied as global settings in
+# user_variables, but are left here to illustrate that
+# each container could have different storage targets.
+storage_hosts:
+  controller00:
+    ip: 172.29.236.11
+    container_vars:
+      cinder_backends:
+        limit_container_types: cinder_volume
+        lvm:
+          volume_group: cinder-volumes
+          volume_driver: cinder.volume.drivers.lvm.LVMVolumeDriver
+          volume_backend_name: LVM_iSCSI
+          iscsi_ip_address: "172.29.244.11"
diff --git a/prototypes/xci/file/noha/user_variables.yml b/prototypes/xci/file/noha/user_variables.yml
new file mode 100644 (file)
index 0000000..7a0b806
--- /dev/null
@@ -0,0 +1,28 @@
+---
+# Copyright 2014, Rackspace US, Inc.
+#
+# 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.
+
+# ##
+# ## This file contains commonly used overrides for convenience. Please inspect
+# ## the defaults for each role to find additional override options.
+# ##
+
+# # Debug and Verbose options.
+debug: false
+
+haproxy_keepalived_external_vip_cidr: "192.168.122.3/32"
+haproxy_keepalived_internal_vip_cidr: "172.29.236.11/32"
+haproxy_keepalived_external_interface: br-vlan
+haproxy_keepalived_internal_interface: br-mgmt
+gnocchi_db_sync_options: ""
diff --git a/prototypes/xci/file/setup-openstack.yml b/prototypes/xci/file/setup-openstack.yml
new file mode 100644 (file)
index 0000000..415c489
--- /dev/null
@@ -0,0 +1,25 @@
+---
+# Copyright 2014, Rackspace US, Inc.
+#
+# 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.
+
+- include: os-keystone-install.yml
+- include: os-glance-install.yml
+- include: os-cinder-install.yml
+- include: os-nova-install.yml
+- include: os-neutron-install.yml
+- include: os-heat-install.yml
+- include: os-horizon-install.yml
+- include: os-swift-install.yml
+- include: os-ironic-install.yml
+- include: os-tempest-install.yml
diff --git a/prototypes/xci/playbooks/configure-localhost.yml b/prototypes/xci/playbooks/configure-localhost.yml
new file mode 100644 (file)
index 0000000..2a55964
--- /dev/null
@@ -0,0 +1,43 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+- hosts: localhost
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/opnfv.yml
+  roles:
+    - role: remove-folders
+    - { role: clone-repository, project: "opnfv/releng", repo: "{{ OPNFV_RELENG_GIT_URL }}", dest: "{{ OPNFV_RELENG_PATH }}", version: "{{ OPNFV_RELENG_VERSION }}" }
+  tasks:
+    - name:  create log directory {{LOG_PATH}}
+      file:
+        path: "{{LOG_PATH}}"
+        state: directory
+        recurse: no
+    # when the deployment is not aio, we use playbook, configure-targethosts.yml, to configure all the hosts
+    - name: copy multihost playbook
+      copy:
+        src: "{{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/configure-targethosts.yml"
+        dest: "{{OPNFV_RELENG_PATH}}/prototypes/xci/playbooks"
+      when: XCI_FLAVOR != "aio"
+    # when the deployment is aio, we overwrite and use playbook, configure-opnfvhost.yml, since everything gets installed on opnfv host
+    - name: copy aio playbook
+      copy:
+        src: "{{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/configure-opnfvhost.yml"
+        dest: "{{OPNFV_RELENG_PATH}}/prototypes/xci/playbooks"
+      when: XCI_FLAVOR == "aio"
+    - name: copy flavor inventory
+      copy:
+        src: "{{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/inventory"
+        dest: "{{OPNFV_RELENG_PATH}}/prototypes/xci/playbooks"
+    - name: copy flavor vars
+      copy:
+        src: "{{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/flavor-vars.yml"
+        dest: "{{OPNFV_RELENG_PATH}}/prototypes/xci/var"
diff --git a/prototypes/xci/playbooks/configure-opnfvhost.yml b/prototypes/xci/playbooks/configure-opnfvhost.yml
new file mode 100644 (file)
index 0000000..8c794c4
--- /dev/null
@@ -0,0 +1,67 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+- hosts: opnfv
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/flavor-vars.yml
+    - ../var/opnfv.yml
+  roles:
+    - role: remove-folders
+    - { role: clone-repository, project: "opnfv/releng", repo: "{{ OPNFV_RELENG_GIT_URL }}", dest: "{{ OPNFV_RELENG_PATH }}", version: "{{ OPNFV_RELENG_VERSION }}" }
+    - { role: clone-repository, project: "openstack/openstack-ansible", repo: "{{ OPENSTACK_OSA_GIT_URL }}", dest: "{{ OPENSTACK_OSA_PATH }}", version: "{{ OPENSTACK_OSA_VERSION }}" }
+    # TODO: this only works for ubuntu/xenial and need to be adjusted for other distros
+    - { role: configure-network, when: ansible_distribution_release == "xenial", src: "../template/opnfv.interface.j2", dest: "/etc/network/interfaces" }
+  tasks:
+    - name: generate SSH keys
+      shell: ssh-keygen -b 2048 -t rsa -f /root/.ssh/id_rsa -q -N ""
+      args:
+        creates: /root/.ssh/id_rsa
+    - name: fetch public key
+      fetch: src="/root/.ssh/id_rsa.pub" dest="/"
+    - name: copy flavor inventory
+      shell: "/bin/cp -rf {{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/inventory {{OPNFV_RELENG_PATH}}/prototypes/xci/playbooks"
+    - name: copy flavor vars
+      shell: "/bin/cp -rf {{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/flavor-vars.yml {{OPNFV_RELENG_PATH}}/prototypes/xci/var"
+    - name: copy openstack_deploy
+      shell: "/bin/cp -rf {{OPENSTACK_OSA_PATH}}/etc/openstack_deploy {{OPENSTACK_OSA_ETC_PATH}}"
+    - name: copy openstack_user_config.yml
+      shell: "/bin/cp -rf {{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/openstack_user_config.yml {{OPENSTACK_OSA_ETC_PATH}}"
+    - name: copy user_variables.yml
+      shell: "/bin/cp -rf {{XCI_FLAVOR_ANSIBLE_FILE_PATH}}/user_variables.yml {{OPENSTACK_OSA_ETC_PATH}}"
+    - name: copy cinder.yml
+      shell: "/bin/cp -rf {{OPNFV_RELENG_PATH}}/prototypes/xci/file/cinder.yml {{OPENSTACK_OSA_ETC_PATH}}/env.d"
+    - name: bootstrap ansible on opnfv host
+      command: "/bin/bash ./scripts/bootstrap-ansible.sh"
+      args:
+        chdir: "{{OPENSTACK_OSA_PATH}}"
+    - name: generate password token
+      command: "python pw-token-gen.py --file {{OPENSTACK_OSA_ETC_PATH}}/user_secrets.yml"
+      args:
+        chdir: "{{OPENSTACK_OSA_PATH}}/scripts"
+    # TODO: We need to get rid of this as soon as the issue is fixed upstream
+    - name: change the haproxy state from disable to enable
+      replace:
+        dest: "{{OPENSTACK_OSA_PATH}}/playbooks/os-keystone-install.yml"
+        regexp: '(\s+)haproxy_state: disabled'
+        replace: '\1haproxy_state: enabled'
+    - name: copy OPNFV OpenStack playbook
+      shell: "/bin/cp -rf {{OPNFV_RELENG_PATH}}/prototypes/xci/file/setup-openstack.yml {{OPENSTACK_OSA_PATH}}/playbooks"
+    # Copy pinned role requirements if we are running as part of daily CI loop
+    - name: copy OPNFV role requirements
+      shell: "/bin/cp -rf {{OPNFV_RELENG_PATH}}/prototypes/xci/file/ansible-role-requirements.yml {{OPENSTACK_OSA_PATH}}"
+      when: XCI_LOOP == "daily"
+- hosts: localhost
+  remote_user: root
+  tasks:
+    - name: Generate authorized_keys
+      shell: "/bin/cat /opnfv/root/.ssh/id_rsa.pub >> ../file/authorized_keys"
+    - name: Append public keys to authorized_keys
+      shell: "/bin/cat /root/.ssh/id_rsa.pub >> ../file/authorized_keys"
similarity index 65%
rename from utils/test/testapi/opnfv_testapi/common/constants.py
rename to prototypes/xci/playbooks/inventory
index 4d39a14..fd9af90 100644 (file)
@@ -1,15 +1,10 @@
+# SPDX-license-identifier: Apache-2.0
 ##############################################################################
-# Copyright (c) 2015 Orange
-# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com
+# Copyright (c) 2017 Ericsson AB and others.
 # All rights reserved. This program and the accompanying materials
 # are made available under the terms of the Apache License, Version 2.0
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-
-
-DEFAULT_REPRESENTATION = "application/json"
-HTTP_BAD_REQUEST = 400
-HTTP_FORBIDDEN = 403
-HTTP_NOT_FOUND = 404
-HTTP_OK = 200
+[opnfv]
+opnfv ansible_ssh_host=192.168.122.2
diff --git a/prototypes/xci/playbooks/provision-vm-nodes.yml b/prototypes/xci/playbooks/provision-vm-nodes.yml
new file mode 100644 (file)
index 0000000..9a32d0b
--- /dev/null
@@ -0,0 +1,32 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+- hosts: localhost
+  remote_user: root
+  vars_files:
+    - ../var/{{ ansible_os_family }}.yml
+    - ../var/opnfv.yml
+  roles:
+    # using these roles here ensures that we can reuse this playbook in different context
+    - role: remove-folders
+    - { role: clone-repository, project: "opnfv/releng", repo: "{{ OPNFV_RELENG_GIT_URL }}", dest: "{{ OPNFV_RELENG_PATH }}", version: "{{ OPNFV_RELENG_VERSION }}" }
+    - { role: clone-repository, project: "opnfv/bifrost", repo: "{{ OPENSTACK_BIFROST_GIT_URL }}", dest: "{{ OPENSTACK_BIFROST_PATH }}", version: "{{ OPENSTACK_BIFROST_VERSION }}" }
+  tasks:
+    - name: combine opnfv/releng and openstack/bifrost scripts/playbooks
+      copy:
+        src: "{{ OPNFV_RELENG_PATH }}/prototypes/bifrost/"
+        dest: "{{ OPENSTACK_BIFROST_PATH }}"
+    - name: destroy VM nodes created by previous deployment
+      command: "/bin/bash ./scripts/destroy-env.sh"
+      args:
+        chdir: "{{ OPENSTACK_BIFROST_PATH }}"
+    - name: create and provision VM nodes for the flavor {{ XCI_FLAVOR }}
+      command: "/bin/bash ./scripts/bifrost-provision.sh"
+      args:
+        chdir: "{{ OPENSTACK_BIFROST_PATH }}"
diff --git a/prototypes/xci/playbooks/roles/clone-repository/tasks/main.yml b/prototypes/xci/playbooks/roles/clone-repository/tasks/main.yml
new file mode 100644 (file)
index 0000000..3f7e091
--- /dev/null
@@ -0,0 +1,14 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+- name: clone "{{ project }}" and checkout "{{ version }}"
+  git:
+    repo: "{{ repo }}"
+    dest: "{{ dest }}"
+    version: "{{ version }}"
diff --git a/prototypes/xci/playbooks/roles/configure-network/tasks/main.yml b/prototypes/xci/playbooks/roles/configure-network/tasks/main.yml
new file mode 100644 (file)
index 0000000..8bc8482
--- /dev/null
@@ -0,0 +1,16 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+# TODO: this role needs to be adjusted for different distros
+- name: configure network for {{ ansible_os_family }} on interface {{ interface }}
+  template:
+    src: "{{ src }}"
+    dest: "{{ dest }}"
+- name: restart ubuntu xenial network service
+  shell: "/sbin/ifconfig {{ interface }} 0 &&/sbin/ifdown -a && /sbin/ifup -a"
diff --git a/prototypes/xci/playbooks/roles/configure-nfs/tasks/main.yml b/prototypes/xci/playbooks/roles/configure-nfs/tasks/main.yml
new file mode 100644 (file)
index 0000000..66dd0af
--- /dev/null
@@ -0,0 +1,36 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+# TODO: this is for xenial and needs to be adjusted for different distros
+- block:
+    - name: make NFS dir
+      file:
+        dest: /images
+        mode: 0777
+        state: directory
+    - name: configure NFS service
+      lineinfile:
+        dest: /etc/services
+        state: present
+        create: yes
+        line: "{{ item }}"
+      with_items:
+        - "nfs        2049/tcp"
+        - "nfs        2049/udp"
+    - name: configure NFS exports on ubuntu xenial
+      copy:
+        src: ../file/exports
+        dest: /etc/exports
+      when: ansible_distribution_release == "xenial"
+    # TODO: the service name might be different on other distros and needs to be adjusted
+    - name: restart ubuntu xenial NFS service
+      service:
+        name: nfs-kernel-server
+        state: restarted
+  when: ansible_distribution_release == "xenial"
diff --git a/prototypes/xci/playbooks/roles/remove-folders/tasks/main.yml b/prototypes/xci/playbooks/roles/remove-folders/tasks/main.yml
new file mode 100644 (file)
index 0000000..ac8c0f7
--- /dev/null
@@ -0,0 +1,20 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+- name: cleanup leftovers of previous deployment
+  file:
+    path: "{{ item }}"
+    state: absent
+    recurse: no
+  with_items:
+    - "{{ OPNFV_RELENG_PATH }}"
+    - "{{ OPENSTACK_BIFROST_PATH }}"
+    - "{{ OPENSTACK_OSA_PATH }}"
+    - "{{ OPENSTACK_OSA_ETC_PATH }}"
+    - "{{ LOG_PATH }} "
diff --git a/prototypes/xci/template/compute.interface.j2 b/prototypes/xci/template/compute.interface.j2
new file mode 100644 (file)
index 0000000..0c5147c
--- /dev/null
@@ -0,0 +1,86 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+
+# Physical interface
+auto {{ interface }}
+iface {{ interface }} inet manual
+
+# Container/Host management VLAN interface
+auto {{ interface }}.10
+iface {{ interface }}.10 inet manual
+    vlan-raw-device {{ interface }}
+
+# OpenStack Networking VXLAN (tunnel/overlay) VLAN interface
+auto {{ interface }}.30
+iface {{ interface }}.30 inet manual
+    vlan-raw-device {{ interface }}
+
+# Storage network VLAN interface (optional)
+auto {{ interface }}.20
+iface {{ interface }}.20 inet manual
+    vlan-raw-device {{ interface }}
+
+# Container/Host management bridge
+auto br-mgmt
+iface br-mgmt inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.10
+    address {{host_info[inventory_hostname].MGMT_IP}}
+    netmask 255.255.252.0
+
+# compute1 VXLAN (tunnel/overlay) bridge config
+auto br-vxlan
+iface br-vxlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.30
+    address {{host_info[inventory_hostname].VXLAN_IP}}
+    netmask 255.255.252.0
+
+# OpenStack Networking VLAN bridge
+auto br-vlan
+iface br-vlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}
+    address {{host_info[inventory_hostname].VLAN_IP}}
+    netmask 255.255.255.0
+    gateway 192.168.122.1
+    offload-sg off
+    # Create veth pair, don't bomb if already exists
+    pre-up ip link add br-vlan-veth type veth peer name eth12 || true
+    # Set both ends UP
+    pre-up ip link set br-vlan-veth up
+    pre-up ip link set eth12 up
+    # Delete veth pair on DOWN
+    post-down ip link del br-vlan-veth || true
+    bridge_ports br-vlan-veth
+
+# Add an additional address to br-vlan
+iface br-vlan inet static
+    # Flat network default gateway
+    # -- This needs to exist somewhere for network reachability
+    # -- from the router namespace for floating IP paths.
+    # -- Putting this here is primarily for tempest to work.
+    address {{host_info[inventory_hostname].VLAN_IP_SECOND}}
+    netmask 255.255.252.0
+    dns-nameserver 8.8.8.8 8.8.4.4
+
+# compute1 Storage bridge
+auto br-storage
+iface br-storage inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.20
+    address {{host_info[inventory_hostname].STORAGE_IP}}
+    netmask 255.255.252.0
diff --git a/prototypes/xci/template/controller.interface.j2 b/prototypes/xci/template/controller.interface.j2
new file mode 100644 (file)
index 0000000..fbaa8b8
--- /dev/null
@@ -0,0 +1,71 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+# Physical interface
+auto {{ interface }}
+iface {{ interface }} inet manual
+
+# Container/Host management VLAN interface
+auto {{ interface }}.10
+iface {{ interface }}.10 inet manual
+    vlan-raw-device {{ interface }}
+
+# OpenStack Networking VXLAN (tunnel/overlay) VLAN interface
+auto {{ interface }}.30
+iface {{ interface }}.30 inet manual
+    vlan-raw-device {{ interface }}
+
+# Storage network VLAN interface (optional)
+auto {{ interface }}.20
+iface {{ interface }}.20 inet manual
+    vlan-raw-device {{ interface }}
+
+# Container/Host management bridge
+auto br-mgmt
+iface br-mgmt inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.10
+    address {{host_info[inventory_hostname].MGMT_IP}}
+    netmask 255.255.252.0
+
+# OpenStack Networking VXLAN (tunnel/overlay) bridge
+#
+# Only the COMPUTE and NETWORK nodes must have an IP address
+# on this bridge. When used by infrastructure nodes, the
+# IP addresses are assigned to containers which use this
+# bridge.
+#
+auto br-vxlan
+iface br-vxlan inet manual
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.30
+
+# OpenStack Networking VLAN bridge
+auto br-vlan
+iface br-vlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}
+    address {{host_info[inventory_hostname].VLAN_IP}}
+    netmask 255.255.255.0
+    gateway 192.168.122.1
+    dns-nameserver 8.8.8.8 8.8.4.4
+
+# compute1 Storage bridge
+auto br-storage
+iface br-storage inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.20
+    address {{host_info[inventory_hostname].STORAGE_IP}}
+    netmask 255.255.252.0
diff --git a/prototypes/xci/template/opnfv.interface.j2 b/prototypes/xci/template/opnfv.interface.j2
new file mode 100644 (file)
index 0000000..fbaa8b8
--- /dev/null
@@ -0,0 +1,71 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+# Physical interface
+auto {{ interface }}
+iface {{ interface }} inet manual
+
+# Container/Host management VLAN interface
+auto {{ interface }}.10
+iface {{ interface }}.10 inet manual
+    vlan-raw-device {{ interface }}
+
+# OpenStack Networking VXLAN (tunnel/overlay) VLAN interface
+auto {{ interface }}.30
+iface {{ interface }}.30 inet manual
+    vlan-raw-device {{ interface }}
+
+# Storage network VLAN interface (optional)
+auto {{ interface }}.20
+iface {{ interface }}.20 inet manual
+    vlan-raw-device {{ interface }}
+
+# Container/Host management bridge
+auto br-mgmt
+iface br-mgmt inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.10
+    address {{host_info[inventory_hostname].MGMT_IP}}
+    netmask 255.255.252.0
+
+# OpenStack Networking VXLAN (tunnel/overlay) bridge
+#
+# Only the COMPUTE and NETWORK nodes must have an IP address
+# on this bridge. When used by infrastructure nodes, the
+# IP addresses are assigned to containers which use this
+# bridge.
+#
+auto br-vxlan
+iface br-vxlan inet manual
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.30
+
+# OpenStack Networking VLAN bridge
+auto br-vlan
+iface br-vlan inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}
+    address {{host_info[inventory_hostname].VLAN_IP}}
+    netmask 255.255.255.0
+    gateway 192.168.122.1
+    dns-nameserver 8.8.8.8 8.8.4.4
+
+# compute1 Storage bridge
+auto br-storage
+iface br-storage inet static
+    bridge_stp off
+    bridge_waitport 0
+    bridge_fd 0
+    bridge_ports {{ interface }}.20
+    address {{host_info[inventory_hostname].STORAGE_IP}}
+    netmask 255.255.252.0
diff --git a/prototypes/xci/var/Debian.yml b/prototypes/xci/var/Debian.yml
new file mode 100644 (file)
index 0000000..d13d080
--- /dev/null
@@ -0,0 +1,11 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+# this is the interface the VM nodes are connected to libvirt network "default"
+interface: "ens3"
diff --git a/prototypes/xci/var/RedHat.yml b/prototypes/xci/var/RedHat.yml
new file mode 100644 (file)
index 0000000..6d03e0f
--- /dev/null
@@ -0,0 +1,10 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+# this is placeholder and left blank intentionally to complete later on
diff --git a/prototypes/xci/var/Suse.yml b/prototypes/xci/var/Suse.yml
new file mode 100644 (file)
index 0000000..6d03e0f
--- /dev/null
@@ -0,0 +1,10 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+# this is placeholder and left blank intentionally to complete later on
diff --git a/prototypes/xci/var/opnfv.yml b/prototypes/xci/var/opnfv.yml
new file mode 100644 (file)
index 0000000..12cb556
--- /dev/null
@@ -0,0 +1,25 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2017 Ericsson AB and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+OPNFV_RELENG_GIT_URL: "{{ lookup('env','OPNFV_RELENG_GIT_URL') }}"
+OPNFV_RELENG_PATH: "{{ lookup('env','OPNFV_RELENG_PATH') }}"
+OPNFV_RELENG_VERSION: "{{ lookup('env','OPNFV_RELENG_VERSION') }}"
+OPENSTACK_BIFROST_GIT_URL: "{{ lookup('env','OPENSTACK_BIFROST_GIT_URL') }}"
+OPENSTACK_BIFROST_PATH: "{{ lookup('env','OPENSTACK_BIFROST_PATH') }}"
+OPENSTACK_BIFROST_VERSION: "{{ lookup('env','OPENSTACK_BIFROST_VERSION') }}"
+OPENSTACK_OSA_GIT_URL: "{{ lookup('env','OPENSTACK_OSA_GIT_URL') }}"
+OPENSTACK_OSA_PATH: "{{ lookup('env','OPENSTACK_OSA_PATH') }}"
+OPENSTACK_OSA_VERSION: "{{ lookup('env','OPENSTACK_OSA_VERSION') }}"
+OPENSTACK_OSA_ETC_PATH: "{{ lookup('env','OPENSTACK_OSA_ETC_PATH') }}"
+XCI_ANSIBLE_PIP_VERSION: "{{ lookup('env','XCI_ANSIBLE_PIP_VERSION') }}"
+XCI_FLAVOR: "{{ lookup('env','XCI_FLAVOR') }}"
+XCI_FLAVOR_ANSIBLE_FILE_PATH: "{{ lookup('env','XCI_FLAVOR_ANSIBLE_FILE_PATH') }}"
+XCI_LOOP: "{{ lookup('env','XCI_LOOP') }}"
+LOG_PATH: "{{ lookup('env','LOG_PATH') }}"
+OPNFV_HOST_IP: "{{ lookup('env','OPNFV_HOST_IP') }}"
diff --git a/prototypes/xci/xci-deploy.sh b/prototypes/xci/xci-deploy.sh
new file mode 100755 (executable)
index 0000000..2fd9be0
--- /dev/null
@@ -0,0 +1,202 @@
+#!/bin/bash
+set -o errexit
+set -o nounset
+set -o pipefail
+
+#-------------------------------------------------------------------------------
+# This script must run as root
+#-------------------------------------------------------------------------------
+if [[ $(whoami) != "root" ]]; then
+    echo "Error: This script must be run as root!"
+    exit 1
+fi
+
+#-------------------------------------------------------------------------------
+# Set environment variables
+#-------------------------------------------------------------------------------
+# The order of sourcing the variable files is significant so please do not
+# change it or things might stop working.
+# - user-vars: variables that can be configured or overriden by user.
+# - pinned-versions: versions to checkout. These can be overriden if you want to
+#   use different/more recent versions of the tools but you might end up using
+#   something that is not verified by OPNFV XCI.
+# - flavor-vars: settings for VM nodes for the chosen flavor.
+# - env-vars: variables for the xci itself and you should not need to change or
+#   override any of them.
+#-------------------------------------------------------------------------------
+# find where are we
+XCI_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+# source user vars
+source $XCI_PATH/config/user-vars
+# source pinned versions
+source $XCI_PATH/config/pinned-versions
+# source flavor configuration
+source "$XCI_PATH/config/${XCI_FLAVOR}-vars"
+# source xci configuration
+source $XCI_PATH/config/env-vars
+
+#-------------------------------------------------------------------------------
+# Log info to console
+#-------------------------------------------------------------------------------
+echo "Info: Starting XCI Deployment"
+echo "Info: Deployment parameters"
+echo "-------------------------------------------------------------------------"
+echo "xci flavor: $XCI_FLAVOR"
+echo "opnfv/releng version: $OPNFV_RELENG_VERSION"
+echo "openstack/bifrost version: $OPENSTACK_BIFROST_VERSION"
+echo "openstack/openstack-ansible version: $OPENSTACK_OSA_VERSION"
+echo "-------------------------------------------------------------------------"
+
+#-------------------------------------------------------------------------------
+# Install ansible on localhost
+#-------------------------------------------------------------------------------
+pip install ansible==$XCI_ANSIBLE_PIP_VERSION
+
+# TODO: The xci playbooks can be put into a playbook which will be done later.
+
+#-------------------------------------------------------------------------------
+# Start provisioning VM nodes
+#-------------------------------------------------------------------------------
+# This playbook
+# - removes directories that were created by the previous xci run
+# - clones opnfv/releng and openstack/bifrost repositories
+# - combines opnfv/releng and openstack/bifrost scripts/playbooks
+# - destorys VMs, removes ironic db, leases, logs
+# - creates and provisions VMs for the chosen flavor
+#-------------------------------------------------------------------------------
+echo "Info: Starting provisining VM nodes using openstack/bifrost"
+echo "-------------------------------------------------------------------------"
+cd $XCI_PATH/playbooks
+ansible-playbook $ANSIBLE_VERBOSITY -i inventory provision-vm-nodes.yml
+echo "-----------------------------------------------------------------------"
+echo "Info: VM nodes are provisioned!"
+source $OPENSTACK_BIFROST_PATH/env-vars
+ironic node-list
+echo
+#-------------------------------------------------------------------------------
+# Configure localhost
+#-------------------------------------------------------------------------------
+# This playbook
+# - removes directories that were created by the previous xci run
+# - clones opnfv/releng repository
+# - creates log directory
+# - copies flavor files such as playbook, inventory, and var file
+#-------------------------------------------------------------------------------
+echo "Info: Configuring localhost for openstack-ansible"
+echo "-----------------------------------------------------------------------"
+cd $XCI_PATH/playbooks
+ansible-playbook $ANSIBLE_VERBOSITY -i inventory configure-localhost.yml
+echo "-----------------------------------------------------------------------"
+echo "Info: Configured localhost host for openstack-ansible"
+
+#-------------------------------------------------------------------------------
+# Configure openstack-ansible deployment host, opnfv
+#-------------------------------------------------------------------------------
+# This playbook
+# - removes directories that were created by the previous xci run
+# - clones opnfv/releng and openstack/openstack-ansible repositories
+# - configures network
+# - generates/prepares ssh keys
+# - bootstraps ansible
+# - copies flavor files to be used by openstack-ansible
+#-------------------------------------------------------------------------------
+echo "Info: Configuring opnfv deployment host for openstack-ansible"
+echo "-----------------------------------------------------------------------"
+cd $OPNFV_RELENG_PATH/prototypes/xci/playbooks
+ansible-playbook $ANSIBLE_VERBOSITY -i inventory configure-opnfvhost.yml
+echo "-----------------------------------------------------------------------"
+echo "Info: Configured opnfv deployment host for openstack-ansible"
+
+#-------------------------------------------------------------------------------
+# Skip the rest if the flavor is aio since the target host for aio is opnfv
+#-------------------------------------------------------------------------------
+if [[ $XCI_FLAVOR == "aio" ]]; then
+    echo "xci: aio has been installed"
+    exit 0
+fi
+
+#-------------------------------------------------------------------------------
+# Configure target hosts for openstack-ansible
+#-------------------------------------------------------------------------------
+# This playbook
+# - adds public keys to target hosts
+# - configures network
+# - configures nfs
+#-------------------------------------------------------------------------------
+echo "Info: Configuring target hosts for openstack-ansible"
+echo "-----------------------------------------------------------------------"
+cd $OPNFV_RELENG_PATH/prototypes/xci/playbooks
+ansible-playbook $ANSIBLE_VERBOSITY -i inventory configure-targethosts.yml
+echo "-----------------------------------------------------------------------"
+echo "Info: Configured target hosts"
+
+#-------------------------------------------------------------------------------
+# Set up target hosts for openstack-ansible
+#-------------------------------------------------------------------------------
+# This is openstack-ansible playbook. Check upstream documentation for details.
+#-------------------------------------------------------------------------------
+echo "Info: Setting up target hosts for openstack-ansible"
+echo "-----------------------------------------------------------------------"
+sudo -E /bin/sh -c "ssh root@$OPNFV_HOST_IP openstack-ansible \
+     $OPENSTACK_OSA_PATH/playbooks/setup-hosts.yml" | \
+     tee $LOG_PATH/setup-hosts.log
+echo "-----------------------------------------------------------------------"
+# check the log to see if we have any error
+if grep -q 'failed=1\|unreachable=1' $LOG_PATH/setup-hosts.log; then
+    echo "Error: OpenStack node setup failed!"
+    exit 1
+fi
+echo "Info: Set up target hosts for openstack-ansible successfuly"
+
+#-------------------------------------------------------------------------------
+# Set up infrastructure
+#-------------------------------------------------------------------------------
+# This is openstack-ansible playbook. Check upstream documentation for details.
+#-------------------------------------------------------------------------------
+echo "Info: Setting up infrastructure"
+echo "-----------------------------------------------------------------------"
+echo "xci: running ansible playbook setup-infrastructure.yml"
+sudo -E /bin/sh -c "ssh root@$OPNFV_HOST_IP openstack-ansible \
+     $OPENSTACK_OSA_PATH/playbooks//setup-infrastructure.yml" | \
+     tee $LOG_PATH/setup-infrastructure.log
+echo "-----------------------------------------------------------------------"
+# check the log to see if we have any error
+if grep -q 'failed=1\|unreachable=1' $LOG_PATH/setup-infrastructure.log; then
+    echo "Error: OpenStack node setup failed!"
+    exit 1
+fi
+
+#-------------------------------------------------------------------------------
+# Verify database cluster
+#-------------------------------------------------------------------------------
+echo "Info: Verifying database cluster"
+echo "-----------------------------------------------------------------------"
+sudo -E /bin/sh -c "ssh root@$OPNFV_HOST_IP ansible -i $OPENSTACK_OSA_PATH/playbooks/inventory/ \
+           galera_container -m shell \
+           -a "mysql -h localhost -e 'show status like \"%wsrep_cluster_%\";'"" \
+           | tee $LOG_PATH/galera.log
+echo "-----------------------------------------------------------------------"
+# check the log to see if we have any error
+if grep -q 'FAILED' $LOG_PATH/galera.log; then
+    echo "Error: Database cluster verification failed!"
+    exit 1
+fi
+echo "Info: Database cluster verification successful!"
+
+#-------------------------------------------------------------------------------
+# Install OpenStack
+#-------------------------------------------------------------------------------
+# This is openstack-ansible playbook. Check upstream documentation for details.
+#-------------------------------------------------------------------------------
+echo "Info: Installing OpenStack on target hosts"
+echo "-----------------------------------------------------------------------"
+sudo -E /bin/sh -c "ssh root@$OPNFV_HOST_IP openstack-ansible \
+     $OPENSTACK_OSA_PATH/playbooks/setup-openstack.yml" | \
+     tee $LOG_PATH/opnfv-setup-openstack.log
+echo "-----------------------------------------------------------------------"
+# check the log to see if we have any error
+if grep -q 'failed=1\|unreachable=1' $LOG_PATH/opnfv-setup-openstack.log; then
+   echo "Error: OpenStack installation failed!"
+   exit 1
+fi
+echo "Info: OpenStack installation is successfully completed!"
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..2d9246e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+from setuptools import setup
+
+setup(
+    name="opnfv",
+    version="master",
+    url="https://www.opnfv.org",
+)
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..e9f5fbb
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,34 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27
+skipsdist = True
+
+[testenv]
+usedevelop = True
+setenv=
+  HOME = {envtmpdir}
+  PYTHONPATH = {toxinidir}
+
+[testenv:jjb]
+deps =
+       -rjjb/test-requirements.txt
+commands=
+       jenkins-jobs test -o job_output -r jjb/
+
+[testenv:modules]
+deps=
+       -rmodules/requirements.txt
+       -rmodules/test-requirements.txt
+commands =
+       nosetests -w modules \
+       --with-xunit \
+       --xunit-file=modules/nosetests.xml \
+       --cover-package=opnfv \
+       --with-coverage \
+       --cover-xml \
+       --cover-html \
+       tests/unit
diff --git a/utils/calculate_version.sh b/utils/calculate_version.sh
deleted file mode 100755 (executable)
index cf929dd..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/bin/bash
-# SPDX-license-identifier: Apache-2.0
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-# Calculates and generates the version tag for the OPNFV objects:
-#     - Docker images
-#     - ISOs
-#     - Artifacts
-
-info ()  {
-    logger -s -t "Calculate_version.info" "$*"
-}
-
-
-error () {
-    logger -s -t "Calculate_version.error" "$*"
-    exit 1
-}
-
-
-#Functions which calculate the version
-function docker_version() {
-    docker_image=$1
-    url_repo="https://registry.hub.docker.com/v2/repositories/${docker_image}/"
-    url_tag="https://registry.hub.docker.com/v2/repositories/${docker_image}/tags/"
-    status=$(curl -s --head -w %{http_code} ${url_repo} -o /dev/null)
-    if [ "${status}" != "200" ]; then
-        error "Cannot access ${url_repo}. Does the image ${docker_image} exist?"
-    fi
-    tag_json=$(curl $url_tag 2>/dev/null | python -mjson.tool | grep ${BASE_VERSION} | head -1)
-    #e.g. tag_json= "name": "brahmaputra.0.2",
-    #special case, for dovetail, not sync with release, tag_json name not headed with arno, etc
-    if [ "${tag_json}" == "" ]; then
-        echo ${BASE_VERSION}.0
-    else
-        tag=$(echo $tag_json | awk '{print $2}' | sed 's/\,//' | sed 's/\"//g')
-        #e.g.: tag=brahmaputra.0.2
-        #special case, for dovetail, not sync with release
-        tag_current_version=$(echo $tag | sed 's/.*\.//')
-        tag_new_version=$(($tag_current_version+1))
-        #e.g.: tag=brahmaputra.0.3
-        echo ${BASE_VERSION}.${tag_new_version}
-    fi
-}
-
-
-function artifact_version() {
-    # To be done
-    error "Not supported yet..."
-}
-
-
-STORAGE_TYPES=(docker artifactrepo)
-TYPE=""
-NAME=""
-
-
-usage="Calculates the version text of one of the following objects.
-
-usage:
-    bash $(basename "$0") [-h|--help] -t|--type docker|artifactrepo -n|--name <object_name>
-
-where:
-    -h|--help      show this help text
-    -t|--type      specify the storage location
-    -n|--name      name of the repository/object
-
-examples:
-    $(basename "$0") -t docker -n opnfv/functest
-    $(basename "$0") -t artifactrepo -n fuel"
-
-
-
-
-# Parse parameters
-while [[ $# > 0 ]]
-    do
-    key="$1"
-    case $key in
-        -h|--help)
-            echo "$usage"
-            exit 0
-            shift
-        ;;
-        -t|--type)
-            TYPE="$2"
-            shift
-        ;;
-        -n|--name)
-            NAME="$2"
-            shift
-        ;;
-        *)
-            error "unknown option $1"
-            exit 1
-        ;;
-    esac
-    shift # past argument or value
-done
-
-if [ -z "$BASE_VERSION" ]; then
-    error "Base version must be specified as environment variable. Ex.: export BASE_VERSION='brahmaputra.0'"
-fi
-
-if [ "${TYPE}" == "" ]; then
-    error "Please specify the type of object to get the version from. $usage"
-fi
-
-if [ "${NAME}" == "" ]; then
-    error "Please specify the name for the given storage type. $usage"
-fi
-
-not_in=1
-for i in "${STORAGE_TYPES[@]}"; do
-    if [[ "${TYPE}" == "$i" ]]; then
-        not_in=0
-    fi
-done
-if [ ${not_in} == 1 ]; then
-    error "Unknown type: ${TYPE}. Available storage types are: [${STORAGE_TYPES[@]}]"
-fi
-
-
-#info "Calculating version for object '${TYPE}' with arguments '${INFO}'..."
-if [ "${TYPE}" == "docker" ]; then
-    docker_version $NAME
-
-elif [ "${TYPE}" == "artifactrepo" ]; then
-    artifact_version $NAME
-fi
index 47fbc91..c99afac 100755 (executable)
@@ -38,6 +38,16 @@ verify_connectivity() {
     error "Can not talk to $ip."
 }
 
+
+swap_to_public() {
+    if [ "$1" != "" ]; then
+        info "Exchanging keystone public IP in rc file to $public_ip"
+        sed -i  "/OS_AUTH_URL/c\export OS_AUTH_URL=\'$public_ip'" $dest_path
+        sed -i 's/internalURL/publicURL/g' $dest_path
+    fi
+}
+
+
 : ${DEPLOY_TYPE:=''}
 
 #Get options
@@ -104,17 +114,20 @@ if [ "$installer_type" == "fuel" ]; then
     #This file contains the mgmt keystone API, we need the public one for our rc file
     admin_ip=$(cat $dest_path | grep "OS_AUTH_URL" | sed 's/^.*\=//' | sed "s/^\([\"']\)\(.*\)\1\$/\2/g" | sed s'/\/$//')
     public_ip=$(sshpass -p r00tme ssh $ssh_options root@${installer_ip} \
-        "ssh ${controller_ip} 'source openrc; openstack endpoint list --long'" \
-        | grep $admin_ip | sed 's/ /\n/g' | grep ^http | head -1) &> /dev/null
+        "ssh ${controller_ip} 'source openrc; openstack endpoint list'" \
+        | grep keystone | grep public | sed 's/ /\n/g' | grep ^http | head -1) &> /dev/null
         #| grep http | head -1 | cut -d '|' -f 4 | sed 's/v1\/.*/v1\//' | sed 's/ //g') &> /dev/null
     #NOTE: this is super ugly sed 's/v1\/.*/v1\//'OS_AUTH_URL
     # but sometimes the output of endpoint-list is like this: http://172.30.9.70:8004/v1/%(tenant_id)s
     # Fuel virtual need a fix
 
-    if [ "$DEPLOY_TYPE" == "virt" ]; then
-        echo "INFO: Changing: internalURL -> publicURL in openrc"
-        sed -i 's/internalURL/publicURL/' $dest_path
+    #convert to v3 URL
+    auth_url=$(cat $dest_path|grep AUTH_URL)
+    if [[ -z `echo $auth_url |grep v3` ]]; then
+        auth_url=$(echo $auth_url |sed "s|'$|v3&|")
     fi
+    sed -i '/AUTH_URL/d' $dest_path
+    echo $auth_url >> $dest_path
 
 elif [ "$installer_type" == "apex" ]; then
     verify_connectivity $installer_ip
@@ -131,7 +144,7 @@ elif [ "$installer_type" == "compass" ]; then
     verify_connectivity $installer_ip
     controller_ip=$(sshpass -p'root' ssh 2>/dev/null $ssh_options root@${installer_ip} \
         'mysql -ucompass -pcompass -Dcompass -e"select *  from cluster;"' \
-        | awk -F"," '{for(i=1;i<NF;i++)if($i~/\"host[1-5]\"/) {print $(i+1);break;}}'  \
+        | awk -F"," '{for(i=1;i<NF;i++)if($i~/\"127.0.0.1\"/) {print $(i+2);break;}}'  \
         | grep -oP "\d+.\d+.\d+.\d+")
 
     if [ -z $controller_ip ]; then
@@ -144,10 +157,19 @@ elif [ "$installer_type" == "compass" ]; then
     sshpass -p root scp 2>/dev/null $ssh_options root@${installer_ip}:~/admin-openrc.sh $dest_path &> /dev/null
 
     info "This file contains the mgmt keystone API, we need the public one for our rc file"
-    public_ip=$(sshpass -p root ssh $ssh_options root@${installer_ip} \
-        "ssh ${controller_ip} 'source /opt/admin-openrc.sh; openstack endpoint show identity '" \
-        | grep publicurl | awk '{print $4}')
+    grep "OS_AUTH_URL.*v2" $dest_path > /dev/null 2>&1
+    if [ $?  -eq 0 ] ; then
+        public_ip=$(sshpass -p root ssh $ssh_options root@${installer_ip} \
+            "ssh ${controller_ip} 'source /opt/admin-openrc.sh; openstack endpoint show identity '" \
+            | grep publicurl | awk '{print $4}')
+    else
+        public_ip=$(sshpass -p root ssh $ssh_options root@${installer_ip} \
+            "ssh ${controller_ip} 'source /opt/admin-openrc.sh; \
+                 openstack endpoint list --interface public --service identity '" \
+            | grep identity | awk '{print $14}')
+    fi
     info "public_ip: $public_ip"
+    swap_to_public $public_ip
 
 
 elif [ "$installer_type" == "joid" ]; then
@@ -179,6 +201,17 @@ elif [ "$installer_type" == "foreman" ]; then
         'source keystonerc_admin;keystone endpoint-list'" \
         | grep $admin_ip | sed 's/ /\n/g' | grep ^http | head -1) &> /dev/null
 
+elif [ "$installer_type" == "daisy" ]; then
+    verify_connectivity $installer_ip
+    cluster=$(sshpass -p r00tme ssh 2>/dev/null $ssh_options root@${installer_ip} \
+            "source ~/daisyrc_admin; daisy cluster-list"|grep active|head -1|awk -F "|" '{print $3}') &> /dev/null
+    if [ -z $cluster ]; then
+        echo "No active cluster detected in daisy"
+        exit 1
+    fi
+
+    sshpass -p r00tme scp 2>/dev/null $ssh_options root@${installer_ip}:/etc/kolla/admin-openrc.sh $dest_path &> /dev/null
+
 else
     error "Installer $installer is not supported by this script"
 fi
@@ -188,13 +221,6 @@ if [ ! -f $dest_path ]; then
     error "There has been an error retrieving the credentials"
 fi
 
-if [ "$public_ip" != "" ]; then
-    info "Exchanging keystone public IP in rc file to $public_ip"
-    sed -i  "/OS_AUTH_URL/c\export OS_AUTH_URL=\'$public_ip'" $dest_path
-fi
-
-
-
 echo "-------- Credentials: --------"
 cat $dest_path
 
diff --git a/utils/installer-adapter/ApexAdapter.py b/utils/installer-adapter/ApexAdapter.py
deleted file mode 100644 (file)
index 17a27b1..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# Author: Jose Lausuch (jose.lausuch@ericsson.com)
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-class ApexAdapter:
-
-    def __init__(self, installer_ip):
-        self.installer_ip = installer_ip
-
-    def get_deployment_info(self):
-        pass
-
-    def get_nodes(self):
-        pass
-
-    def get_controller_ips(self):
-        pass
-
-    def get_compute_ips(self):
-        pass
-
-    def get_file_from_installer(self, origin, target, options=None):
-        pass
-
-    def get_file_from_controller(self, origin, target, ip=None, options=None):
-        pass
diff --git a/utils/installer-adapter/CompassAdapter.py b/utils/installer-adapter/CompassAdapter.py
deleted file mode 100644 (file)
index 47cbc64..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# Author: Jose Lausuch (jose.lausuch@ericsson.com)
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-class CompassAdapter:
-
-    def __init__(self, installer_ip):
-        self.installer_ip = installer_ip
-
-    def get_deployment_info(self):
-        pass
-
-    def get_nodes(self):
-        pass
-
-    def get_controller_ips(self):
-        pass
-
-    def get_compute_ips(self):
-        pass
-
-    def get_file_from_installer(self, origin, target, options=None):
-        pass
-
-    def get_file_from_controller(self, origin, target, ip=None, options=None):
-        pass
diff --git a/utils/installer-adapter/FuelAdapter.py b/utils/installer-adapter/FuelAdapter.py
deleted file mode 100644 (file)
index 672fd51..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# Author: Jose Lausuch (jose.lausuch@ericsson.com)
-#         George Paraskevopoulos (geopar@intracom-telecom.com)
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-import SSHUtils as ssh_utils
-import RelengLogger as rl
-
-
-class FuelAdapter:
-
-    def __init__(self, installer_ip, user="root", password="r00tme"):
-        self.installer_ip = installer_ip
-        self.installer_user = user
-        self.installer_password = password
-        self.installer_connection = ssh_utils.get_ssh_client(
-            installer_ip,
-            self.installer_user,
-            password=self.installer_password)
-        self.logger = rl.Logger("Handler").getLogger()
-
-    def runcmd_fuel_installer(self, cmd):
-        _, stdout, stderr = (self
-                             .installer_connection
-                             .exec_command(cmd))
-        error = stderr.readlines()
-        if len(error) > 0:
-            self.logger.error("error %s" % ''.join(error))
-            return error
-        output = ''.join(stdout.readlines())
-        return output
-
-    def runcmd_fuel_nodes(self):
-        return self.runcmd_fuel_installer('fuel nodes')
-
-    def runcmd_fuel_env(self):
-        return self.runcmd_fuel_installer('fuel env')
-
-    def get_clusters(self):
-        environments = []
-        output = self.runcmd_fuel_env()
-        lines = output.rsplit('\n')
-        if len(lines) < 2:
-            self.logger.infp("No environments found in the deployment.")
-            return None
-        else:
-            fields = lines[0].rsplit(' | ')
-
-            index_id = -1
-            index_status = -1
-            index_name = -1
-            index_release_id = -1
-
-            for i in range(0, len(fields) - 1):
-                if "id" in fields[i]:
-                    index_id = i
-                elif "status" in fields[i]:
-                    index_status = i
-                elif "name" in fields[i]:
-                    index_name = i
-                elif "release_id" in fields[i]:
-                    index_release_id = i
-
-            # order env info
-            for i in range(2, len(lines) - 1):
-                fields = lines[i].rsplit(' | ')
-                dict = {"id": fields[index_id].strip(),
-                        "status": fields[index_status].strip(),
-                        "name": fields[index_name].strip(),
-                        "release_id": fields[index_release_id].strip()}
-                environments.append(dict)
-
-        return environments
-
-    def get_nodes(self, options=None):
-        nodes = []
-        output = self.runcmd_fuel_nodes()
-        lines = output.rsplit('\n')
-        if len(lines) < 2:
-            self.logger.info("No nodes found in the deployment.")
-            return None
-        else:
-            # get fields indexes
-            fields = lines[0].rsplit(' | ')
-
-            index_id = -1
-            index_status = -1
-            index_name = -1
-            index_cluster = -1
-            index_ip = -1
-            index_mac = -1
-            index_roles = -1
-            index_online = -1
-
-            for i in range(0, len(fields) - 1):
-                if "id" in fields[i]:
-                    index_id = i
-                elif "status" in fields[i]:
-                    index_status = i
-                elif "name" in fields[i]:
-                    index_name = i
-                elif "cluster" in fields[i]:
-                    index_cluster = i
-                elif "ip" in fields[i]:
-                    index_ip = i
-                elif "mac" in fields[i]:
-                    index_mac = i
-                elif "roles " in fields[i]:
-                    index_roles = i
-                elif "online" in fields[i]:
-                    index_online = i
-
-            # order nodes info
-            for i in range(2, len(lines) - 1):
-                fields = lines[i].rsplit(' | ')
-                dict = {"id": fields[index_id].strip(),
-                        "status": fields[index_status].strip(),
-                        "name": fields[index_name].strip(),
-                        "cluster": fields[index_cluster].strip(),
-                        "ip": fields[index_ip].strip(),
-                        "mac": fields[index_mac].strip(),
-                        "roles": fields[index_roles].strip(),
-                        "online": fields[index_online].strip()}
-                if options and options['cluster']:
-                    if fields[index_cluster].strip() == options['cluster']:
-                        nodes.append(dict)
-                else:
-                    nodes.append(dict)
-
-        return nodes
-
-    def get_controller_ips(self, options):
-        nodes = self.get_nodes(options=options)
-        controllers = []
-        for node in nodes:
-            if "controller" in node["roles"]:
-                controllers.append(node['ip'])
-        return controllers
-
-    def get_compute_ips(self, options=None):
-        nodes = self.get_nodes(options=options)
-        computes = []
-        for node in nodes:
-            if "compute" in node["roles"]:
-                computes.append(node['ip'])
-        return computes
-
-    def get_deployment_info(self):
-        str = "Deployment details:\n"
-        str += "\tInstaller:  Fuel\n"
-        str += "\tScenario:   Unknown\n"
-        sdn = "None"
-        clusters = self.get_clusters()
-        str += "\tN.Clusters: %s\n" % len(clusters)
-        for cluster in clusters:
-            cluster_dic = {'cluster': cluster['id']}
-            str += "\tCluster info:\n"
-            str += "\t   ID:          %s\n" % cluster['id']
-            str += "\t   NAME:        %s\n" % cluster['name']
-            str += "\t   STATUS:      %s\n" % cluster['status']
-            nodes = self.get_nodes(options=cluster_dic)
-            num_nodes = len(nodes)
-            for node in nodes:
-                if "opendaylight" in node['roles']:
-                    sdn = "OpenDaylight"
-                elif "onos" in node['roles']:
-                    sdn = "ONOS"
-            num_controllers = len(
-                self.get_controller_ips(options=cluster_dic))
-            num_computes = len(self.get_compute_ips(options=cluster_dic))
-            ha = False
-            if num_controllers > 1:
-                ha = True
-
-            str += "\t   HA:          %s\n" % ha
-            str += "\t   NUM.NODES:   %s\n" % num_nodes
-            str += "\t   CONTROLLERS: %s\n" % num_controllers
-            str += "\t   COMPUTES:    %s\n" % num_computes
-            str += "\t   SDN CONTR.:  %s\n\n" % sdn
-        str += self.runcmd_fuel_nodes()
-        return str
-
-    def get_file_from_installer(self, remote_path, local_path, options=None):
-        self.logger.debug("Fetching %s from %s" %
-                          (remote_path, self.installer_ip))
-        get_file_result = ssh_utils.get_file(self.installer_connection,
-                                             remote_path,
-                                             local_path)
-        if get_file_result is None:
-            self.logger.error("SFTP failed to retrieve the file.")
-            return 1
-        self.logger.info("%s successfully copied from Fuel to %s" %
-                         (remote_path, local_path))
-
-    def get_file_from_controller(self,
-                                 remote_path,
-                                 local_path,
-                                 ip=None,
-                                 user='root',
-                                 options=None):
-        if ip is None:
-            controllers = self.get_controller_ips(options=options)
-            if len(controllers) == 0:
-                self.logger.info("No controllers found in the deployment.")
-                return 1
-            else:
-                target_ip = controllers[0]
-        else:
-            target_ip = ip
-
-        installer_jumphost = {
-            'ip': self.installer_ip,
-            'username': self.installer_user,
-            'password': self.installer_password
-        }
-        controller_conn = ssh_utils.get_ssh_client(
-            target_ip,
-            user,
-            jumphost=installer_jumphost)
-
-        self.logger.debug("Fetching %s from %s" %
-                          (remote_path, target_ip))
-
-        get_file_result = ssh_utils.get_file(controller_conn,
-                                             remote_path,
-                                             local_path)
-        if get_file_result is None:
-            self.logger.error("SFTP failed to retrieve the file.")
-            return 1
-        self.logger.info("%s successfully copied from %s to %s" %
-                         (remote_path, target_ip, local_path))
diff --git a/utils/installer-adapter/InstallerHandler.py b/utils/installer-adapter/InstallerHandler.py
deleted file mode 100644 (file)
index b81b806..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-##############################################################################
-# Copyright (c) 2015 Ericsson AB and others.
-# Author: Jose Lausuch (jose.lausuch@ericsson.com)
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-from FuelAdapter import FuelAdapter
-from ApexAdapter import ApexAdapter
-from CompassAdapter import CompassAdapter
-from JoidAdapter import JoidAdapter
-
-
-INSTALLERS = ["fuel", "apex", "compass", "joid"]
-
-
-class InstallerHandler:
-
-    def __init__(self,
-                 installer,
-                 installer_ip,
-                 installer_user,
-                 installer_pwd=None):
-        self.installer = installer.lower()
-        self.installer_ip = installer_ip
-        self.installer_user = installer_user
-        self.installer_pwd = installer_pwd
-
-        if self.installer == INSTALLERS[0]:
-            self.InstallerAdapter = FuelAdapter(self.installer_ip,
-                                                self.installer_user,
-                                                self.installer_pwd)
-        elif self.installer == INSTALLERS[1]:
-            self.InstallerAdapter = ApexAdapter(self.installer_ip)
-        elif self.installer == INSTALLERS[2]:
-            self.InstallerAdapter = CompassAdapter(self.installer_ip)
-        elif self.installer == INSTALLERS[3]:
-            self.InstallerAdapter = JoidAdapter(self.installer_ip)
-        else:
-            print("Installer %s is  not valid. "
-                  "Please use one of the followings: %s"
-                  % (self.installer, INSTALLERS))
-            exit(1)
-
-    def get_deployment_info(self):
-        return self.InstallerAdapter.get_deployment_info()
-
-    def get_nodes(self, options=None):
-        return self.InstallerAdapter.get_nodes(options=options)
-
-    def get_controller_ips(self, options=None):
-        return self.InstallerAdapter.get_controller_ips(options=options)
-
-    def get_compute_ips(self, options=None):
-        return self.InstallerAdapter.get_compute_ips(options=options)
-
-    def get_file_from_installer(self,
-                                remote_path,
-                                local_path,
-                                options=None):
-        return self.InstallerAdapter.get_file_from_installer(remote_path,
-                                                             local_path,
-                                                             options=options)
-
-    def get_file_from_controller(self,
-                                 remote_path,
-                                 local_path,
-                                 ip=None,
-                                 options=None):
-        return self.InstallerAdapter.get_file_from_controller(remote_path,
-                                                              local_path,
-                                                              ip=ip,
-                                                              options=options)
-
-    def get_all(self):
-        pass
diff --git a/utils/installer-adapter/JoidAdapter.py b/utils/installer-adapter/JoidAdapter.py
deleted file mode 100644 (file)
index be8c2eb..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 Ericsson AB and others.
-# Author: Jose Lausuch (jose.lausuch@ericsson.com)
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-class JoidAdapter:
-
-    def __init__(self, installer_ip):
-        self.installer_ip = installer_ip
-
-    def get_deployment_info(self):
-        pass
-
-    def get_nodes(self):
-        pass
-
-    def get_controller_ips(self):
-        pass
-
-    def get_compute_ips(self):
-        pass
-
-    def get_file_from_installer(self, origin, target, options=None):
-        pass
-
-    def get_file_from_controller(self, origin, target, ip=None, options=None):
-        pass
diff --git a/utils/installer-adapter/SSHUtils.py b/utils/installer-adapter/SSHUtils.py
deleted file mode 100644 (file)
index c938886..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-##############################################################################
-# Copyright (c) 2015 Ericsson AB and others.
-# Authors: George Paraskevopoulos (geopar@intracom-telecom.com)
-#          Jose Lausuch (jose.lausuch@ericsson.com)
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-import paramiko
-import RelengLogger as rl
-import os
-
-logger = rl.Logger('SSHUtils').getLogger()
-
-
-def get_ssh_client(hostname, username, password=None, jumphost=None):
-    client = None
-    try:
-        if jumphost is None:
-            client = paramiko.SSHClient()
-        else:
-            client = JumpHostHopClient()
-            client.configure_jump_host(jumphost['ip'],
-                                       jumphost['username'],
-                                       jumphost['password'])
-
-        if client is None:
-            raise Exception('Could not connect to client')
-
-        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-        client.connect(hostname,
-                       username=username,
-                       password=password)
-        return client
-    except Exception, e:
-        logger.error(e)
-        return None
-
-
-def get_file(ssh_conn, src, dest):
-    try:
-        sftp = ssh_conn.open_sftp()
-        sftp.get(src, dest)
-        return True
-    except Exception, e:
-        logger.error("Error [get_file(ssh_conn, '%s', '%s']: %s" %
-                     (src, dest, e))
-        return None
-
-
-def put_file(ssh_conn, src, dest):
-    try:
-        sftp = ssh_conn.open_sftp()
-        sftp.put(src, dest)
-        return True
-    except Exception, e:
-        logger.error("Error [put_file(ssh_conn, '%s', '%s']: %s" %
-                     (src, dest, e))
-        return None
-
-
-class JumpHostHopClient(paramiko.SSHClient):
-    '''
-    Connect to a remote server using a jumphost hop
-    '''
-    def __init__(self, *args, **kwargs):
-        self.logger = rl.Logger("JumpHostHopClient").getLogger()
-        self.jumphost_ssh = None
-        self.jumphost_transport = None
-        self.jumphost_channel = None
-        self.jumphost_ip = None
-        self.jumphost_ssh_key = None
-        self.local_ssh_key = os.path.join(os.getcwd(), 'id_rsa')
-        super(JumpHostHopClient, self).__init__(*args, **kwargs)
-
-    def configure_jump_host(self, jh_ip, jh_user, jh_pass,
-                            jh_ssh_key='/root/.ssh/id_rsa'):
-        self.jumphost_ip = jh_ip
-        self.jumphost_ssh_key = jh_ssh_key
-        self.jumphost_ssh = paramiko.SSHClient()
-        self.jumphost_ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-        self.jumphost_ssh.connect(jh_ip,
-                                  username=jh_user,
-                                  password=jh_pass)
-        self.jumphost_transport = self.jumphost_ssh.get_transport()
-
-    def connect(self, hostname, port=22, username='root', password=None,
-                pkey=None, key_filename=None, timeout=None, allow_agent=True,
-                look_for_keys=True, compress=False, sock=None, gss_auth=False,
-                gss_kex=False, gss_deleg_creds=True, gss_host=None,
-                banner_timeout=None):
-        try:
-            if self.jumphost_ssh is None:
-                raise Exception('You must configure the jump '
-                                'host before calling connect')
-
-            get_file_res = get_file(self.jumphost_ssh,
-                                    self.jumphost_ssh_key,
-                                    self.local_ssh_key)
-            if get_file_res is None:
-                raise Exception('Could\'t fetch SSH key from jump host')
-            jumphost_key = (paramiko.RSAKey
-                            .from_private_key_file(self.local_ssh_key))
-
-            self.jumphost_channel = self.jumphost_transport.open_channel(
-                "direct-tcpip",
-                (hostname, 22),
-                (self.jumphost_ip, 22))
-
-            self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-            super(JumpHostHopClient, self).connect(hostname,
-                                                   username=username,
-                                                   pkey=jumphost_key,
-                                                   sock=self.jumphost_channel)
-            os.remove(self.local_ssh_key)
-        except Exception, e:
-            self.logger.error(e)
diff --git a/utils/installer-adapter/example.py b/utils/installer-adapter/example.py
deleted file mode 100644 (file)
index 804d79c..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-# This is an example of usage of this Tool
-# Author: Jose Lausuch (jose.lausuch@ericsson.com)
-
-from InstallerHandler import InstallerHandler
-
-fuel_handler = InstallerHandler(installer='fuel',
-                                installer_ip='10.20.0.2',
-                                installer_user='root',
-                                installer_pwd='r00tme')
-print("Nodes in cluster 1:\n%s\n" %
-      fuel_handler.get_nodes(options={'cluster': '1'}))
-print("Nodes in cluster 2:\n%s\n" %
-      fuel_handler.get_nodes(options={'cluster': '2'}))
-print("Nodes:\n%s\n" % fuel_handler.get_nodes())
-print("Controller nodes:\n%s\n" % fuel_handler.get_controller_ips())
-print("Compute nodes:\n%s\n" % fuel_handler.get_compute_ips())
-print("\n%s\n" % fuel_handler.get_deployment_info())
-fuel_handler.get_file_from_installer('/root/deploy/dea.yaml', './dea.yaml')
-fuel_handler.get_file_from_controller(
-    '/etc/neutron/neutron.conf', './neutron.conf')
-fuel_handler.get_file_from_controller(
-    '/root/openrc', './openrc')
index 9ef4298..8fce2e0 100755 (executable)
@@ -30,8 +30,6 @@ EOF
 }
 
 main () {
-    dir=$(cd $(dirname $0); pwd)
-
     #tests
     if [[ -z $jenkinsuser || -z $jenkinshome ]]; then
         echo "jenkinsuser or home not defined, please edit this file to define it"
@@ -60,17 +58,6 @@ main () {
       fi
     fi
 
-
-    if [ -d /etc/monit/conf.d ]; then
-        monitconfdir="/etc/monit/conf.d/"
-    elif [ -d /etc/monit.d ]; then
-        monitconfdir="/etc/monit.d"
-    else
-        echo "Could not determine the location of the monit configuration file."
-        echo "Make sure monit is installed."
-        exit 1
-    fi
-
     #make pid dir
     pidfile="/var/run/$jenkinsuser/jenkins_jnlp_pid"
     if ! [ -d /var/run/$jenkinsuser/ ]; then
@@ -94,58 +81,83 @@ main () {
                 exit 1
             fi
         fi
-    fi
 
-    makemonit () {
-        echo "Writing the following as monit config:"
+        if [ -d /etc/monit/conf.d ]; then
+            monitconfdir="/etc/monit/conf.d/"
+        elif [ -d /etc/monit.d ]; then
+            monitconfdir="/etc/monit.d"
+        else
+            echo "Could not determine the location of the monit configuration file."
+            echo "Make sure monit is installed."
+            exit 1
+        fi
+
+        chown=$(type -p chown)
+        mkdir=$(type -p mkdir)
+
+        makemonit () {
+            echo "Writing the following as monit config:"
         cat << EOF | tee $monitconfdir/jenkins
+check directory jenkins_piddir path /var/run/$jenkinsuser
+if does not exist then exec "$mkdir -p /var/run/$jenkinsuser"
+if failed uid $jenkinsuser then exec "$chown $jenkinsuser /var/run/$jenkinsuser"
+if failed gid $jenkinsuser then exec "$chown :$jenkinsuser /var/run/$jenkinsuser"
+
 check process jenkins with pidfile /var/run/$jenkinsuser/jenkins_jnlp_pid
-start program = "/usr/bin/sudo -u $jenkinsuser /bin/bash -c 'cd $dir; export started_monit=true; $0 $@' with timeout 60 seconds"
+start program = "/usr/bin/sudo -u $jenkinsuser /bin/bash -c 'cd $jenkinshome; export started_monit=true; $0 $@' with timeout 60 seconds"
 stop program = "/bin/bash -c '/bin/kill \$(/bin/cat /var/run/$jenkinsuser/jenkins_jnlp_pid)'"
+depends on jenkins_piddir
 EOF
-    }
+        }
+
+        if [[ -f $monitconfdir/jenkins ]]; then
+            #test for diff
+            if [[ "$(diff $monitconfdir/jenkins <(echo "\
+check directory jenkins_piddir path /var/run/$jenkinsuser
+if does not exist then exec \"$mkdir -p /var/run/$jenkinsuser\"
+if failed uid $jenkinsuser then exec \"$chown $jenkinsuser /var/run/$jenkinsuser\"
+if failed gid $jenkinsuser then exec \"$chown :$jenkinsuser /var/run/$jenkinsuser\"
 
-    if [[ -f $monitconfdir/jenkins ]]; then
-        #test for diff
-        if [[ "$(diff $monitconfdir/jenkins <(echo "\
 check process jenkins with pidfile /var/run/$jenkinsuser/jenkins_jnlp_pid
-start program = \"/usr/bin/sudo -u $jenkinsuser /bin/bash -c 'cd $dir; export started_monit=true; $0 $@' with timeout 60 seconds\"
-stop program = \"/bin/bash -c '/bin/kill \$(/bin/cat /var/run/$jenkinsuser/jenkins_jnlp_pid)'\"\
+start program = \"/usr/bin/sudo -u $jenkinsuser /bin/bash -c 'cd $jenkinshome; export started_monit=true; $0 $@' with timeout 60 seconds\"
+stop program = \"/bin/bash -c '/bin/kill \$(/bin/cat /var/run/$jenkinsuser/jenkins_jnlp_pid)'\"
+depends on jenkins_piddir\
 ") )" ]]; then
-            echo "Updating monit config..."
+                echo "Updating monit config..."
+                makemonit $@
+            fi
+        else
             makemonit $@
         fi
-    else
-        makemonit $@
     fi
 
-if [[ $started_monit == "true" ]]; then
-    wget --timestamping https://build.opnfv.org/ci/jnlpJars/slave.jar && true
-    chown $jenkinsuser:$jenkinsuser slave.jar
+    if [[ $started_monit == "true" ]]; then
+        wget --timestamping https://build.opnfv.org/ci/jnlpJars/slave.jar && true
+        chown $jenkinsuser:$jenkinsuser slave.jar
 
-    if [[ -f /var/run/$jenkinsuser/jenkins_jnlp_pid ]]; then
-        echo "pid file found"
-        if ! kill -0 "$(/bin/cat /var/run/$jenkinsuser/jenkins_jnlp_pid)"; then
-            echo "no java process running cleaning up pid file"
-            rm -f /var/run/$jenkinsuser/jenkins_jnlp_pid;
-        else
-            echo "java connection process found and running already running quitting."
-            exit 1
+        if [[ -f /var/run/$jenkinsuser/jenkins_jnlp_pid ]]; then
+            echo "pid file found"
+            if ! kill -0 "$(/bin/cat /var/run/$jenkinsuser/jenkins_jnlp_pid)"; then
+                echo "no java process running cleaning up pid file"
+                rm -f /var/run/$jenkinsuser/jenkins_jnlp_pid;
+            else
+                echo "java connection process found and running already running quitting."
+                exit 1
+            fi
         fi
-    fi
 
-    if [[ $run_in_foreground == true ]]; then
-        $connectionstring
+        if [[ $run_in_foreground == true ]]; then
+            $connectionstring
+        else
+            exec $connectionstring &
+            echo $! > /var/run/$jenkinsuser/jenkins_jnlp_pid
+        fi
     else
-        exec $connectionstring &
-        echo $! > /var/run/$jenkinsuser/jenkins_jnlp_pid
+        echo "you are ready to start monit"
+        echo "eg: service monit start"
+        echo "example debug mode if you are having problems:  /usr/bin/monit -Ivv -c /etc/monit.conf "
+        exit 0
     fi
-else
-    echo "you are ready to start monit"
-    echo "eg: service monit start"
-    echo "example debug mode if you are having problems:  /usr/bin/monit -Ivv -c /etc/monit.conf "
-    exit 0
-fi
 }
 
 usage() {
@@ -167,7 +179,8 @@ usage: $0 [OPTIONS]
  -t  test the connection string by connecting without monit
  -f  test firewall
 
-Example: $0 -j /home/jenkins/ -u jenkins -n lab1 -s 727fdefoofoofoofoofoofoofof800
+Example: $0 -j /home/jenkins -u jenkins -n lab1 -s 727fdefoofoofoofoofoofoofof800
+note: a trailing slash on -j /home/jenkins will break the script
 EOF
 
     exit 1
index 48ab9ab..0a37be2 100644 (file)
@@ -1,3 +1,4 @@
+---
 # Vnic configuration for foreman deploy
 
 network:
index eb9028b..15e64c4 100644 (file)
@@ -1,3 +1,4 @@
+---
 # Vnic configuration for fuel deploy
 
 network:
index 4c08f3d..0dd902f 100755 (executable)
 # -p PASSWORD, --password=PASSWORD
 #                       [Mandatory] Account Password for UCSM Login
 # -f FILE, --file=FILE
-#                       [Optional] Yaml file with network config you want to set for POD
-#                       If not present only current network config will be printed
+#                       [Optional] Yaml file with network config you
+#                       want to set for POD
+#                       If not present only current network config
+#                       will be printed
 #
 
 import getpass
@@ -32,12 +34,14 @@ import platform
 import yaml
 import time
 import sys
-from UcsSdk import *
-from collections import defaultdict
+from UcsSdk import LsmaintAck, LsPower, LsServer, OrgOrg
+from UcsSdk import UcsHandle, VnicEther, VnicEtherIf, YesOrNo
+
 
 POD_PREFIX = "POD-2"
 INSTALLER = "POD-21"
 
+
 def getpassword(prompt):
     if platform.system() == "Linux":
         return getpass.unix_getpass(prompt=prompt)
@@ -51,7 +55,8 @@ def get_servers(handle=None):
     """
     Return list of servers
     """
-    orgObj = handle.GetManagedObject(None, OrgOrg.ClassId(), {OrgOrg.DN : "org-root"})[0]
+    orgObj = handle.GetManagedObject(
+        None, OrgOrg.ClassId(), {OrgOrg.DN: "org-root"})[0]
     servers = handle.GetManagedObject(orgObj, LsServer.ClassId())
     for server in servers:
         if server.Type == 'instance' and POD_PREFIX in server.Dn:
@@ -63,10 +68,10 @@ def set_boot_policy(handle=None, server=None, policy=None):
     Modify Boot policy of server
     """
     obj = handle.GetManagedObject(None, LsServer.ClassId(), {
-            LsServer.DN: server.Dn})
+        LsServer.DN: server.Dn})
     handle.SetManagedObject(obj, LsServer.ClassId(), {
-            LsServer.BOOT_POLICY_NAME: policy} )
-    print " Configured boot policy: {}".format(policy)
+        LsServer.BOOT_POLICY_NAME: policy})
+    print(" Configured boot policy: {}".format(policy))
 
 
 def ack_pending(handle=None, server=None):
@@ -74,30 +79,32 @@ def ack_pending(handle=None, server=None):
     Acknowledge pending state of server
     """
     handle.AddManagedObject(server, LsmaintAck.ClassId(), {
-            LsmaintAck.DN: server.Dn + "/ack",
-            LsmaintAck.DESCR:"",
-            LsmaintAck.ADMIN_STATE:"trigger-immediate",
-            LsmaintAck.SCHEDULER:"",
-            LsmaintAck.POLICY_OWNER:"local"}, True)
-    print " Pending-reboot -> Acknowledged."
+        LsmaintAck.DN: server.Dn + "/ack",
+        LsmaintAck.DESCR: "",
+        LsmaintAck.ADMIN_STATE: "trigger-immediate",
+        LsmaintAck.SCHEDULER: "",
+        LsmaintAck.POLICY_OWNER: "local"}, True)
+    print(" Pending-reboot -> Acknowledged.")
 
 
 def boot_server(handle=None, server=None):
     """
     Boot server (when is in power-off state)
     """
-    obj = handle.GetManagedObject(None, LsServer.ClassId(), {LsServer.DN: server.Dn})
+    obj = handle.GetManagedObject(
+        None, LsServer.ClassId(), {LsServer.DN: server.Dn})
     handle.AddManagedObject(obj, LsPower.ClassId(), {
-            LsPower.DN: server.Dn + "/power",
-            LsPower.STATE:"admin-up"}, True)
-    print " Booting."
+        LsPower.DN: server.Dn + "/power",
+        LsPower.STATE: "admin-up"}, True)
+    print(" Booting.")
 
 
 def get_vnics(handle=None, server=None):
     """
     Return list of vnics for given server
     """
-    vnics = handle.ConfigResolveChildren(VnicEther.ClassId(), server.Dn, None, YesOrNo.TRUE)
+    vnics = handle.ConfigResolveChildren(
+        VnicEther.ClassId(), server.Dn, None, YesOrNo.TRUE)
     return vnics.OutConfigs.GetChild()
 
 
@@ -105,28 +112,36 @@ def get_network_config(handle=None):
     """
     Print current network config
     """
-    print "\nCURRENT NETWORK CONFIG:"
-    print " d - default, t - tagged"
+    print("\nCURRENT NETWORK CONFIG:")
+    print(" d - default, t - tagged")
     for server in get_servers(handle):
-        print ' {}'.format(server.Name)
-        print '  Boot policy: {}'.format(server.OperBootPolicyName)
+        print(' {}'.format(server.Name))
+        print('  Boot policy: {}'.format(server.OperBootPolicyName))
         for vnic in get_vnics(handle, server):
-            print '  {}'.format(vnic.Name)
-            print '   {}'.format(vnic.Addr)
-            vnicIfs = handle.ConfigResolveChildren(VnicEtherIf.ClassId(), vnic.Dn, None, YesOrNo.TRUE)
+            print('  {}'.format(vnic.Name))
+            print('   {}'.format(vnic.Addr))
+            vnicIfs = handle.ConfigResolveChildren(
+                VnicEtherIf.ClassId(), vnic.Dn, None, YesOrNo.TRUE)
             for vnicIf in vnicIfs.OutConfigs.GetChild():
                 if vnicIf.DefaultNet == 'yes':
-                    print '    Vlan: {}d'.format(vnicIf.Vnet)
+                    print('    Vlan: {}d'.format(vnicIf.Vnet))
                 else:
-                    print '    Vlan: {}t'.format(vnicIf.Vnet)
+                    print('    Vlan: {}t'.format(vnicIf.Vnet))
 
 
-def add_interface(handle=None, lsServerDn=None, vnicEther=None, templName=None, order=None, macAddr=None):
+def add_interface(handle=None,
+                  lsServerDn=None,
+                  vnicEther=None,
+                  templName=None,
+                  order=None,
+                  macAddr=None):
     """
     Add interface to server specified by server.DN name
     """
-    print " Adding interface: {}, template: {}, server.Dn: {}".format(vnicEther, templName, lsServerDn)
-    obj = handle.GetManagedObject(None, LsServer.ClassId(), {LsServer.DN:lsServerDn})
+    print(" Adding interface: {}, template: {}, server.Dn: {}".format(
+        vnicEther, templName, lsServerDn))
+    obj = handle.GetManagedObject(
+        None, LsServer.ClassId(), {LsServer.DN: lsServerDn})
     vnicEtherDn = lsServerDn + "/ether-" + vnicEther
     params = {
         VnicEther.STATS_POLICY_NAME: "default",
@@ -146,8 +161,9 @@ def remove_interface(handle=None, vnicEtherDn=None):
     """
     Remove interface specified by Distinguished Name (vnicEtherDn)
     """
-    print " Removing interface: {}".format(vnicEtherDn)
-    obj = handle.GetManagedObject(None, VnicEther.ClassId(), {VnicEther.DN:vnicEtherDn})
+    print(" Removing interface: {}".format(vnicEtherDn))
+    obj = handle.GetManagedObject(
+        None, VnicEther.ClassId(), {VnicEther.DN: vnicEtherDn})
     handle.RemoveManagedObject(obj)
 
 
@@ -165,32 +181,37 @@ def set_network(handle=None, yamlFile=None):
     Configure VLANs on POD according specified network
     """
     # add interfaces and bind them with vNIC templates
-    print "\nRECONFIGURING VNICs..."
+    print("\nRECONFIGURING VNICs...")
     pod_data = read_yaml_file(yamlFile)
     network = pod_data['network']
 
     for index, server in enumerate(get_servers(handle)):
         # Assign template to interface
         for iface, data in network.iteritems():
-            add_interface(handle, server.Dn, iface, data['template'], data['order'], data['mac-list'][index])
+            add_interface(handle, server.Dn, iface, data['template'], data[
+                          'order'], data['mac-list'][index])
 
-        # Remove other interfaces which have not assigned required vnic template
+        # Remove other interfaces which have not assigned required vnic
+        # template
         vnics = get_vnics(handle, server)
         for vnic in vnics:
-            if not any(data['template'] in vnic.OperNwTemplName for iface, data in network.iteritems()):
+            if not any(data['template'] in vnic.OperNwTemplName for
+                       iface, data in network.iteritems()):
                 remove_interface(handle, vnic.Dn)
-                print "  {} removed, template: {}".format(vnic.Name, vnic.OperNwTemplName)
+                print("  {} removed, template: {}".format(
+                    vnic.Name, vnic.OperNwTemplName))
 
         # Set boot policy template
-        if not INSTALLER in server.Dn:
+        if INSTALLER not in server.Dn:
             set_boot_policy(handle, server, pod_data['boot-policy'])
 
 
 if __name__ == "__main__":
-    print "\n*** SKIPING RECONFIGURATION.***\n"
+    print("\n*** SKIPING RECONFIGURATION.***\n")
     sys.exit(0)
     # Latest urllib2 validate certs by default
-    # The process wide "revert to the old behaviour" hook is to monkeypatch the ssl module
+    # The process wide "revert to the old behaviour" hook is to monkeypatch
+    # the ssl module
     # https://bugs.python.org/issue22417
     import ssl
     if hasattr(ssl, '_create_unverified_context'):
@@ -198,14 +219,15 @@ if __name__ == "__main__":
     try:
         handle = UcsHandle()
         parser = optparse.OptionParser()
-        parser.add_option('-i', '--ip',dest="ip",
-                        help="[Mandatory] UCSM IP Address")
-        parser.add_option('-u', '--username',dest="userName",
-                        help="[Mandatory] Account Username for UCSM Login")
-        parser.add_option('-p', '--password',dest="password",
-                        help="[Mandatory] Account Password for UCSM Login")
-        parser.add_option('-f', '--file',dest="yamlFile",
-                        help="[Optional] Yaml file contains network config you want to set on UCS POD1")
+        parser.add_option('-i', '--ip', dest="ip",
+                          help="[Mandatory] UCSM IP Address")
+        parser.add_option('-u', '--username', dest="userName",
+                          help="[Mandatory] Account Username for UCSM Login")
+        parser.add_option('-p', '--password', dest="password",
+                          help="[Mandatory] Account Password for UCSM Login")
+        parser.add_option('-f', '--file', dest="yamlFile",
+                          help=("[Optional] Yaml file contains network "
+                                "config you want to set on UCS POD1"))
         (options, args) = parser.parse_args()
 
         if not options.ip:
@@ -215,26 +237,27 @@ if __name__ == "__main__":
             parser.print_help()
             parser.error("Provide UCSM UserName")
         if not options.password:
-            options.password=getpassword("UCSM Password:")
+            options.password = getpassword("UCSM Password:")
 
         handle.Login(options.ip, options.userName, options.password)
 
         # Change vnic template if specified in cli option
-        if (options.yamlFile != None):
+        if (options.yamlFile is not None):
             set_network(handle, options.yamlFile)
             time.sleep(5)
 
-        print "\nWait until Overall Status of all nodes is OK..."
-        timeout = time.time() + 60*10   #10 minutes timeout
+        print("\nWait until Overall Status of all nodes is OK...")
+        timeout = time.time() + 60 * 10  # 10 minutes timeout
         while True:
             list_of_states = []
             for server in get_servers(handle):
                 if server.OperState == "power-off":
-                    boot_server(handle,server)
+                    boot_server(handle, server)
                 if server.OperState == "pending-reboot":
-                    ack_pending(handle,server)
+                    ack_pending(handle, server)
                 list_of_states.append(server.OperState)
-            print " {}, {} seconds remains.".format(list_of_states, round(timeout-time.time()))
+            print(" {}, {} seconds remains.".format(
+                list_of_states, round(timeout - time.time())))
             if all(state == "ok" for state in list_of_states):
                 break
             if time.time() > timeout:
@@ -246,11 +269,12 @@ if __name__ == "__main__":
 
         handle.Logout()
 
-    except Exception, err:
+    except Exception as err:
         handle.Logout()
-        print "Exception:", str(err)
-        import traceback, sys
-        print '-'*60
+        print("Exception:", str(err))
+        import traceback
+        import sys
+        print('-' * 60)
         traceback.print_exc(file=sys.stdout)
-        print '-'*60
+        print('-' * 60)
         sys.exit(1)
index 876efed..2f2cc41 100644 (file)
@@ -28,56 +28,55 @@ from apiclient.errors import HttpError
 
 import argparse
 import json
-import os
 import sys
 
 api = {
-  'projects': {},
-  'docs': {},
-  'releases': {},
+    'projects': {},
+    'docs': {},
+    'releases': {},
 }
 
 releases = [
-  'arno.2015.1.0',
-  'arno.2015.2.0',
-  'brahmaputra.1.0',
+    'arno.2015.1.0',
+    'arno.2015.2.0',
+    'brahmaputra.1.0',
 ]
 
 # List of file extensions to filter out
 ignore_extensions = [
-  '.buildinfo',
-  '.woff',
-  '.ttf',
-  '.svg',
-  '.eot',
-  '.pickle',
-  '.doctree',
-  '.js',
-  '.png',
-  '.css',
-  '.gif',
-  '.jpeg',
-  '.jpg',
-  '.bmp',
+    '.buildinfo',
+    '.woff',
+    '.ttf',
+    '.svg',
+    '.eot',
+    '.pickle',
+    '.doctree',
+    '.js',
+    '.png',
+    '.css',
+    '.gif',
+    '.jpeg',
+    '.jpg',
+    '.bmp',
 ]
 
 
 parser = argparse.ArgumentParser(
-             description='OPNFV Artifacts JSON Generator')
+    description='OPNFV Artifacts JSON Generator')
 
 parser.add_argument(
-        '-k',
-        dest='key',
-        default='',
-        help='API Key for Google Cloud Storage')
+    '-k',
+    dest='key',
+    default='',
+    help='API Key for Google Cloud Storage')
 
 parser.add_argument(
-        '-p',
-        default=None,
-        dest='pretty',
-        action='store_const',
-        const=2,
-        help='pretty print the output')
+    '-p',
+    default=None,
+    dest='pretty',
+    action='store_const',
+    const=2,
+    help='pretty print the output')
 
 # Parse and assign arguments
 args = parser.parse_args()
@@ -130,7 +129,6 @@ def has_logs(gerrit_review):
     return False
 
 
-
 def has_ignorable_extension(filename):
     for extension in ignore_extensions:
         if filename.lower().endswith(extension):
@@ -148,11 +146,11 @@ def get_results(key):
     files = storage.objects().list(bucket='artifacts.opnfv.org',
                                    fields='nextPageToken,'
                                           'items('
-                                              'name,'
-                                              'mediaLink,'
-                                              'updated,'
-                                              'contentType,'
-                                              'size'
+                                   'name,'
+                                   'mediaLink,'
+                                   'updated,'
+                                   'contentType,'
+                                   'size'
                                           ')')
     while (files is not None):
         sites = files.execute()
@@ -173,7 +171,8 @@ def get_results(key):
 
             project = site_split[0]
             name = '/'.join(site_split[1:])
-            proxy = "http://build.opnfv.org/artifacts.opnfv.org/%s" % site['name']
+            proxy = "http://build.opnfv.org/artifacts.opnfv.org/%s" % site[
+                'name']
             if name.endswith('.html'):
                 href = "http://artifacts.opnfv.org/%s" % site['name']
                 href_type = 'view'
@@ -183,7 +182,7 @@ def get_results(key):
 
             gerrit = has_gerrit_review(site_split)
             logs = False  # has_logs(gerrit)
-            documentation = has_documentation(site_split)
+            documentation = has_documentation(site_split)
             release = has_release(site_split)
 
             category = 'project'
index 87cee78..9099657 100644 (file)
@@ -15,18 +15,21 @@ export PATH=$PATH:/usr/local/bin/
 git_sha1="$(git rev-parse HEAD)"
 res_build_date=${1:-$(date -u +"%Y-%m-%d_%H-%M-%S")}
 project=$PROJECT
-branch=${GIT_BRANCH##*/}
+branch=${BRANCH##*/}
 testbed=$NODE_NAME
 dir_result="${HOME}/opnfv/$project/results/${branch}"
 # src: https://wiki.opnfv.org/display/INF/Hardware+Infrastructure
-# + intel-pod3 (vsperf)
+# + intel-pod12 (vsperf)
 node_list=(\
-'lf-pod1' 'lf-pod2' 'intel-pod2' 'intel-pod3' \
+'lf-pod1' 'lf-pod2' 'intel-pod2' 'intel-pod12' \
 'intel-pod5' 'intel-pod6' 'intel-pod7' 'intel-pod8' \
-'ericsson-pod2' 'ericsson-pod3' 'ericsson-pod4' \
-'ericsson-virtual2' 'ericsson-virtual3' 'ericsson-virtual4' 'ericsson-virtual5' \
+'ericsson-pod1' 'ericsson-pod2' \
+'ericsson-virtual1' 'ericsson-virtual2'  'ericsson-virtual3' \
+'ericsson-virtual4' 'ericsson-virtual5' 'ericsson-virtual12' \
 'arm-pod1' 'arm-pod3' \
-'huawei-pod1' 'huawei-pod2' 'huawei-virtual1' 'huawei-virtual2' 'huawei-virtual3' 'huawei-virtual4')
+'huawei-pod1' 'huawei-pod2' 'huawei-pod3' 'huawei-pod4' 'huawei-pod5' \
+'huawei-pod6' 'huawei-pod7' 'huawei-pod12' \
+'huawei-virtual1' 'huawei-virtual2' 'huawei-virtual3' 'huawei-virtual4')
 
 
 if [[ ! " ${node_list[@]} " =~ " ${testbed} " ]]; then
@@ -53,7 +56,7 @@ if [ -d "$dir_result" ]; then
         else
             gsutil ls gs://artifacts.opnfv.org/"$project"/ &>/dev/null
             if [ $? != 0 ]; then
-                echo "Not possible to push results to artifact: gsutil not installed.";
+                echo "Not possible to push results to artifact: some error happened when using gsutil";
             else
                 echo "Uploading logs to artifact $project_artifact"
                 gsutil -m cp -r "$dir_result"/* gs://artifacts.opnfv.org/"$project_artifact"/ >/dev/null 2>&1
diff --git a/utils/test-sign-artifact.sh b/utils/test-sign-artifact.sh
deleted file mode 100755 (executable)
index f09b7f4..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-
-export PATH=$PATH:/usr/local/bin/
-
-# clone releng repository
-echo "Cloning releng repository..."
-[ -d releng ] && rm -rf releng
-git clone https://gerrit.opnfv.org/gerrit/releng $WORKSPACE/releng/ &> /dev/null
-#this is where we import the siging key
-if [ -f $WORKSPACE/releng/utils/gpg_import_key.sh ]; then 
-  source $WORKSPACE/releng/utils/gpg_import_key.sh
-fi
-
-artifact="foo"
-echo foo > foo
-
-testsign () {
-  echo "Signing artifact: ${artifact}"
-  gpg2 -vvv --batch \
-    --default-key opnfv-helpdesk@rt.linuxfoundation.org  \
-    --passphrase besteffort \
-    --detach-sig $artifact
-}
-
-testsign
-
diff --git a/utils/test/dashboard.tar.gz b/utils/test/dashboard.tar.gz
deleted file mode 100644 (file)
index ef85f90..0000000
Binary files a/utils/test/dashboard.tar.gz and /dev/null differ
index aaf776f..eb29ce8 100644 (file)
@@ -30,7 +30,7 @@ def publish_docs(url, creds=None, body=None):
 
 def _get_docs_nr(url, creds=None, body=None):
     res_data = _get('{}/_search?size=0'.format(url), creds=creds, body=body)
-    print type(res_data), res_data
+    print(type(res_data), res_data)
     return res_data['hits']['total']
 
 
index ff801b4..98ce209 100644 (file)
@@ -21,4 +21,4 @@ def get_format(project, case):
 
 if __name__ == '__main__':
     fmt = get_format('functest', 'vping_ssh')
-    print fmt
+    print(fmt)
index 55578bd..40d9202 100644 (file)
@@ -2,7 +2,8 @@ import json
 
 from jinja2 import Environment, PackageLoader
 
-env = Environment(loader=PackageLoader('dashboard', 'elastic2kibana/templates'))
+env = Environment(loader=PackageLoader('dashboard',
+                                       'elastic2kibana/templates'))
 env.filters['jsonify'] = json.dumps
 
 
index ef485ba..75d361f 100644 (file)
@@ -6,7 +6,8 @@ def _convert_value(value):
 
 
 def _convert_duration(duration):
-    if (isinstance(duration, str) or isinstance(duration, unicode)) and ':' in duration:
+    if ((isinstance(duration, str) or
+            isinstance(duration, unicode)) and ':' in duration):
         hours, minutes, seconds = duration.split(":")
         hours = _convert_value(hours)
         minutes = _convert_value(minutes)
@@ -42,11 +43,11 @@ def format_normal(testcase):
         testcase_tests = float(testcase_details['tests'])
         testcase_failures = float(testcase_details['failures'])
         if testcase_tests != 0:
-            testcase_details['success_percentage'] = 100 * (testcase_tests - testcase_failures) / testcase_tests
+            testcase_details['success_percentage'] = 100 * \
+                (testcase_tests - testcase_failures) / testcase_tests
         else:
             testcase_details['success_percentage'] = 0
 
-
     return found
 
 
@@ -115,28 +116,33 @@ def format_onos(testcase):
     """
     testcase_details = testcase['details']
 
-    if 'FUNCvirNet' not in testcase_details or 'FUNCvirNetL3' not in testcase_details:
+    if ('FUNCvirNet' not in testcase_details or
+            'FUNCvirNetL3' not in testcase_details):
         return False
 
     funcvirnet_details = testcase_details['FUNCvirNet']['status']
-    funcvirnet_stats = _get_statistics(funcvirnet_details, ('Case result',), ('PASS', 'FAIL'))
+    funcvirnet_stats = _get_statistics(
+        funcvirnet_details, ('Case result',), ('PASS', 'FAIL'))
     funcvirnet_passed = funcvirnet_stats['PASS']
     funcvirnet_failed = funcvirnet_stats['FAIL']
     funcvirnet_all = funcvirnet_passed + funcvirnet_failed
 
     funcvirnetl3_details = testcase_details['FUNCvirNetL3']['status']
-    funcvirnetl3_stats = _get_statistics(funcvirnetl3_details, ('Case result',), ('PASS', 'FAIL'))
+    funcvirnetl3_stats = _get_statistics(
+        funcvirnetl3_details, ('Case result',), ('PASS', 'FAIL'))
     funcvirnetl3_passed = funcvirnetl3_stats['PASS']
     funcvirnetl3_failed = funcvirnetl3_stats['FAIL']
     funcvirnetl3_all = funcvirnetl3_passed + funcvirnetl3_failed
 
     testcase_details['FUNCvirNet'] = {
-        'duration': _convert_duration(testcase_details['FUNCvirNet']['duration']),
+        'duration':
+        _convert_duration(testcase_details['FUNCvirNet']['duration']),
         'tests': funcvirnet_all,
         'failures': funcvirnet_failed
     }
     testcase_details['FUNCvirNetL3'] = {
-        'duration': _convert_duration(testcase_details['FUNCvirNetL3']['duration']),
+        'duration':
+        _convert_duration(testcase_details['FUNCvirNetL3']['duration']),
         'tests': funcvirnetl3_all,
         'failures': funcvirnetl3_failed
     }
index 688f55f..e33252d 100644 (file)
@@ -27,7 +27,8 @@ parser.add_argument('-ld', '--latest-days',
                     metavar='N',
                     help='get entries old at most N days from mongodb and'
                          ' parse those that are not already in elasticsearch.'
-                         ' If not present, will get everything from mongodb, which is the default')
+                         ' If not present, will get everything from mongodb,'
+                         ' which is the default')
 
 args = parser.parse_args()
 CONF = APIConfig().parse(args.config_file)
@@ -37,6 +38,7 @@ tmp_docs_file = './mongo-{}.json'.format(uuid.uuid4())
 
 
 class DocumentVerification(object):
+
     def __init__(self, doc):
         super(DocumentVerification, self).__init__()
         self.doc = doc
@@ -55,8 +57,8 @@ class DocumentVerification(object):
         for key, value in self.doc.items():
             if key in mandatory_fields:
                 if value is None:
-                    logger.info("Skip testcase '%s' because field '%s' missing" %
-                                (self.doc_id, key))
+                    logger.info("Skip testcase '%s' because field "
+                                "'%s' missing" % (self.doc_id, key))
                     self.skip = True
                 else:
                     mandatory_fields.remove(key)
@@ -131,10 +133,12 @@ class DocumentPublisher(object):
             self._publish()
 
     def _publish(self):
-        status, data = elastic_access.publish_docs(self.elastic_url, self.creds, self.doc)
+        status, data = elastic_access.publish_docs(
+            self.elastic_url, self.creds, self.doc)
         if status > 300:
             logger.error('Publish record[{}] failed, due to [{}]'
-                         .format(self.doc, json.loads(data)['error']['reason']))
+                         .format(self.doc,
+                                 json.loads(data)['error']['reason']))
 
     def _fix_date(self, date_string):
         if isinstance(date_string, dict):
@@ -163,7 +167,8 @@ class DocumentsPublisher(object):
 
     def export(self):
         if self.days > 0:
-            past_time = datetime.datetime.today() - datetime.timedelta(days=self.days)
+            past_time = datetime.datetime.today(
+            ) - datetime.timedelta(days=self.days)
             query = '''{{
                           "project_name": "{}",
                           "case_name": "{}",
@@ -182,7 +187,7 @@ class DocumentsPublisher(object):
         try:
             subprocess.check_call(cmd)
             return self
-        except Exception, err:
+        except Exception as err:
             logger.error("export mongodb failed: %s" % err)
             self._remove()
             exit(-1)
@@ -217,7 +222,8 @@ class DocumentsPublisher(object):
                    }}'''.format(self.project, self.case, self.days)
         else:
             raise Exception('Update days must be non-negative')
-        self.existed_docs = elastic_access.get_docs(self.elastic_url, self.creds, body)
+        self.existed_docs = elastic_access.get_docs(
+            self.elastic_url, self.creds, body)
         return self
 
     def publish(self):
index cd337cd..dfa9cc2 100644 (file)
@@ -1,3 +1,4 @@
+---
 qtip:
     -
         name: compute_test_suite
@@ -18,7 +19,7 @@ qtip:
                 fields:
                     - field: details.index
     -
-        name:storage_test_suite
+        name: storage_test_suite
         format: qpi
         test_family: storage
         visualizations:
index ee01900..7e3662c 100644 (file)
@@ -9,7 +9,8 @@ from dashboard.common import elastic_access
 logger = logging.getLogger('clear_kibana')
 logger.setLevel(logging.DEBUG)
 file_handler = logging.FileHandler('/var/log/{}.log'.format('clear_kibana'))
-file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
+file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: '
+                                            '%(message)s'))
 logger.addHandler(file_handler)
 
 
@@ -21,12 +22,17 @@ def delete_all(url, es_creds):
 
 
 if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description='Delete saved kibana searches, visualizations and dashboards')
-    parser.add_argument('-e', '--elasticsearch-url', default='http://localhost:9200',
-                        help='the url of elasticsearch, defaults to http://localhost:9200')
+    parser = argparse.ArgumentParser(
+        description=('Delete saved kibana searches, '
+                     'visualizations and dashboards'))
+    parser.add_argument('-e', '--elasticsearch-url',
+                        default='http://localhost:9200',
+                        help=('the url of elasticsearch, '
+                              'defaults to http://localhost:9200'))
 
     parser.add_argument('-u', '--elasticsearch-username', default=None,
-                        help='The username with password for elasticsearch in format username:password')
+                        help=('The username with password for elasticsearch '
+                              'in format username:password'))
 
     args = parser.parse_args()
     base_elastic_url = args.elasticsearch_url
@@ -38,4 +44,3 @@ if __name__ == '__main__':
 
     for url in urls:
         delete_all(url, es_creds)
-
diff --git a/utils/test/declaration/addtestcase.php b/utils/test/declaration/addtestcase.php
deleted file mode 100644 (file)
index 0e5bed6..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-function sendPostData($url, $post){
-  $ch = curl_init($url);
-  $headers= array('Accept: application/json','Content-Type: application/json');
-  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
-  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-  curl_setopt($ch, CURLOPT_POSTFIELDS,$post);
-  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
-  $result = curl_exec($ch);
-  curl_close($ch);
-  return $result;
-}
-
-if(isset($_REQUEST['url'])){
-   $url=$_REQUEST['url'];
-}
-if(isset($_REQUEST['name'])){
-   $name=$_REQUEST['name'];
-}
-if(isset($_REQUEST['desc'])){
-   $desc=$_REQUEST['desc'];
-}
-if(isset($_REQUEST['project'])){
-
-   $url_send=$_REQUEST['project'];
-   $url_send="http://testresults.opnfv.org:80/test/api/v1/projects/".$url_send."/cases";
-   $str_data=array('url'=>$url,'name'=>$name,'description'=>$desc);
-   $str_data=json_encode($str_data);
-   $res=sendPostData($url_send, $str_data);
-   echo '<div class="alert alert-success"> <strong>Success!</strong> Added New test Case  </div>';
-
-}else{
-
-   echo '<div class="alert alert-danger"> <strong>Error!</strong> Failed to Add New test Case  </div>';
-
-}
-
-?>
-
diff --git a/utils/test/declaration/index.php b/utils/test/declaration/index.php
deleted file mode 100644 (file)
index b2c5d03..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <title>OPNFV DashBoard</title>
-  <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
-  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
-  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
-<script>
-$(function() {
-
-  $('form#new_testcase').on('submit', function(){
-    var selected = $('select#sel_pro2').find("option:selected").val();
-    var uri = $('input#uri').val();
-    var name = $('input#name').val();
-    var desc = $('textarea#desc').val();
-    var new_url="http://testresults.opnfv.org:80/test/api/v1/projects/"+selected+"/cases";
-    $.post("addtestcase.php", {"project":selected,"url":uri,"name":name,"description":desc}, function(result){
-        $("div#result").html(result);
-    });
-  });
-
-});
-
-$(function() {
-
-  $('select#sel1').on('change', function(){
-    var selected = $(this).find("option:selected").val();
-    var new_url="http://testresults.opnfv.org:80/test/api/v1/projects/"+selected+"/cases";
-    //$.post('testcases.php', {project: selected});
-    console.log(selected);
-    $.post("testcases.php", {project: selected}, function(result){
-        $("div#4a").html(result);
-    });
-
-  });
-
-});
-</script>
-<style>
-body {
-  padding : 10px ;
-}
-
-#exTab1 .tab-content {
-  color : black;
-  padding : 5px 15px;
-}
-
-#exTab2 h3 {
-  color : white;
-  background-color: #428bca;
-  padding : 5px 15px;
-}
-
-/* remove border radius for the tab */
-
-#exTab1 .nav-pills > li > a {
-  border-radius: 0;
-}
-
-/* change border radius for the tab , apply corners on top*/
-
-#exTab3 .nav-pills > li > a {
-  border-radius: 4px 4px 0 0 ;
-}
-
-#exTab3 .tab-content {
-  color : white;
-  background-color: #428bca;
-  padding : 5px 15px;
-}
-
-</style>
-</head>
-<body>
-
-<div class="container">
-  <h1>OPNFV DASHBOARD: </h1></div>
-<div id="exTab1" class="container">
-  <ul class="nav nav-pills">
-    <li class="active">
-      <a href="#1a" data-toggle="tab">PODS</a>
-    </li>
-    <li><a href="#2a" data-toggle="tab">PROJECTS</a>
-    </li>
-    <li><a href="#3a" data-toggle="tab">TESTCASES</a>
-    </li>
-    <li><a href="#5a" data-toggle="tab">ADD TESTCASE</a>
-    </li>
-    <li><a href="http://testresults.opnfv.org/kibana_dashboards/" >RESULTS</a>
-    </li>
-  </ul>
-  <div class="tab-content clearfix">
-    <div class="tab-pane active" id="1a">
-       <table class="table table-striped">
-       <thead>
-    <tr>
-      <th>#</th>
-      <th>Pod Name</th>
-      <th>Creation Date</th>
-      <th>Role</th>
-      <th>Mode</th>
-    </tr>
-  </thead>
-       <?php
-       $url = "http://testresults.opnfv.org:80/test/api/v1/pods";
-        $response = file_get_contents($url);
-       $data = json_decode($response);
-       $pods = $data->pods;
-       $i=1;
-       foreach ( $pods as $pod ){
-
-               $column_str="";
-               $column_str="<tr><td>".$i."</td>";
-               $column_str=$column_str."<td>".$pod->name."</td>";
-               $column_str= $column_str."<td>".$pod->creation_date."</td>";
-               $column_str= $column_str."<td>".$pod->role."</td>";
-               $column_str= $column_str."<td>".$pod->mode."</td>";
-               $column_str= $column_str."</tr>";
-               echo $column_str;
-               $i=$i+1;
-       }
-       ?>
-       </table>
-    </div>
-    <div class="tab-pane" id="2a">
- <table class="table table-striped">
-        <thead>
-    <tr>
-      <th>#</th>
-      <th>Project</th>
-      <th>Creation Date</th>
-    </tr>
-  </thead>
- <?php
-        $url = "http://testresults.opnfv.org:80/test/api/v1/projects";
-        $response = file_get_contents($url);
-        $data = json_decode($response);
-       $projects=$data->projects;
-       $i=0;
-       foreach ( $projects as $project ){
-
-                $column_str="";
-                $column_str="<tr><td>".$i."</td>";
-                $column_str=$column_str."<td>".$project->name."</td>";
-                $column_str= $column_str."<td>".$project->creation_date."</td>";
-                $column_str= $column_str."</tr>";
-                echo $column_str;
-                $i=$i+1;
-        }
-?>
-       </table>
-    </div>
-    <div class="tab-pane" id="3a">
-<div class="form-group">
-  <label for="sel1">Select list:</label>
-  <select class="form-control" id="sel1">
-<?php
-       $url = "http://testresults.opnfv.org:80/test/api/v1/projects";
-        $response = file_get_contents($url);
-        $data = json_decode($response);
-        $projects=$data->projects;
-        $i=0;
-       $firstvalue=$projects[0]->name;
-        foreach ( $projects as $project ){
-                $column_str="";
-                $column_str="<option>".$project->name."</option>";
-                echo $column_str;
-        }
-
-?>
-</select>
-</div>
-    <div class="tab-pane" id="4a">
-       <?php
-               require "testcases.php";
-       ?>
-    </div>
-    </div>
-    <div class="tab-pane" id="5a">
-       <form role="form" id="new_testcase">
-<div class="form-group">
-  <label for="sel1">Select list:</label>
-  <select class="form-control" id="sel_pro2">
-<?php
-        $url = "http://testresults.opnfv.org:80/test/api/v1/projects";
-        $response = file_get_contents($url);
-        $data = json_decode($response);
-        $projects=$data->projects;
-        $i=0;
-        $firstvalue=$projects[0]->name;
-        foreach ( $projects as $project ){
-                $column_str="";
-                $column_str="<option>".$project->name."</option>";
-                echo $column_str;
-        }
-?>
-</select>
-</div>
-<div class="form-group"> <!-- Name field -->
-               <label class="control-label " for="name">TestCase URI</label>
-               <input class="form-control" id="uri" name="uri" type="text"/>
-       </div>
-<div class="form-group"> <!-- Name field -->
-               <label class="control-label " for="name">TestCase Name</label>
-               <input class="form-control" id="name" name="name" type="text"/>
-       </div>
-<div class="form-group"> <!-- Name field -->
-               <label class="control-label " for="name">Description</label>
-       <textarea class="form-control" rows="5" id="desc"></textarea>
-       </div>
-  <button type="submit" class="btn btn-default">Submit</button>
-</form>
-    </div>
-<div class="container" id="result"></div>
-  </div>
-</div>
-</body>
-</html>
diff --git a/utils/test/declaration/testcases.php b/utils/test/declaration/testcases.php
deleted file mode 100644 (file)
index 2064580..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-       if(isset($_REQUEST['project'])){
-           $selected=$_REQUEST['project'];
-       }
-       else{
-            $url = "http://testresults.opnfv.org:80/test/api/v1/projects";
-            $response = file_get_contents($url);
-            $data = json_decode($response);
-            $projects=$data->projects;
-           $selected=$projects[0]->name;
-       }
-       $new_url="http://testresults.opnfv.org:80/test/api/v1/projects/".$selected."/cases";
-        $response = file_get_contents($new_url);
-        $data = json_decode($response);
-        $testcases=$data->testcases;
-        $i=0;
-        $column_str="";
-        $column_str=$column_str."<table class=\"table table-striped\"><tr>";
-        $column_str=$column_str."<th>#</th><th>Test Case Name</th>";
-        $column_str=$column_str."<th>Creation Date</th>";
-        $column_str=$column_str."<th>Description</th></tr>";
-        foreach ( $testcases as $testcase ){
-               $i=$i+1;
-               $column_str=$column_str."<tr>";
-               $column_str=$column_str."<td>".$i."</td>";
-               $column_str=$column_str."<td>".$testcase->name."</td>";
-               $column_str=$column_str."<td>".$testcase->creation_date."</td>";
-               $column_str=$column_str."<td>".$testcase->description."</td>";
-               $column_str=$column_str."</tr>";
-
-       }
-        $column_str=$column_str."</table>";
-        echo $column_str;
-
-?>
-
diff --git a/utils/test/reporting/api/api/__init__.py b/utils/test/reporting/api/api/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/reporting/api/api/conf.py b/utils/test/reporting/api/api/conf.py
new file mode 100644 (file)
index 0000000..5897d4f
--- /dev/null
@@ -0,0 +1 @@
+base_url = 'http://testresults.opnfv.org/test/api/v1'
diff --git a/utils/test/reporting/api/api/handlers/__init__.py b/utils/test/reporting/api/api/handlers/__init__.py
new file mode 100644 (file)
index 0000000..bcda664
--- /dev/null
@@ -0,0 +1,19 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from tornado.web import RequestHandler
+
+
+class BaseHandler(RequestHandler):
+    def _set_header(self):
+        self.set_header('Access-Control-Allow-Origin', '*')
+        self.set_header('Access-Control-Allow-Headers',
+                        'Content-Type, Content-Length, Authorization, \
+                        Accept, X-Requested-With , PRIVATE-TOKEN')
+        self.set_header('Access-Control-Allow-Methods',
+                        'PUT, POST, GET, DELETE, OPTIONS')
diff --git a/utils/test/reporting/api/api/handlers/landing.py b/utils/test/reporting/api/api/handlers/landing.py
new file mode 100644 (file)
index 0000000..749916f
--- /dev/null
@@ -0,0 +1,169 @@
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import requests
+
+from tornado.escape import json_encode
+from tornado.escape import json_decode
+
+from api.handlers import BaseHandler
+from api import conf
+
+
+class FiltersHandler(BaseHandler):
+    def get(self):
+        self._set_header()
+
+        filters = {
+            'filters': {
+                'status': ['success', 'warning', 'danger'],
+                'projects': ['functest', 'yardstick'],
+                'installers': ['apex', 'compass', 'fuel', 'joid'],
+                'version': ['colorado', 'master'],
+                'loops': ['daily', 'weekly', 'monthly'],
+                'time': ['10 days', '30 days']
+            }
+        }
+        return self.write(json_encode(filters))
+
+
+class ScenariosHandler(BaseHandler):
+    def post(self):
+        self._set_header()
+
+        body = json_decode(self.request.body)
+        args = self._get_args(body)
+
+        scenarios = self._get_result_data(self._get_scenarios(), args)
+
+        return self.write(json_encode(dict(scenarios=scenarios)))
+
+    def _get_result_data(self, data, args):
+        data = self._filter_status(data, args)
+        return {s: self._get_scenario_result(s, data[s], args) for s in data}
+
+    def _filter_status(self, data, args):
+        return {k: v for k, v in data.items() if v['status'] in args['status']}
+
+    def _get_scenario_result(self, scenario, data, args):
+        result = {
+            'status': data.get('status'),
+            'installers': self._get_installers_result(data['installers'], args)
+        }
+        return result
+
+    def _get_installers_result(self, data, args):
+        func = self._get_installer_result
+        return {k: func(k, data.get(k, {}), args) for k in args['installers']}
+
+    def _get_installer_result(self, installer, data, args):
+        projects = data.get(args['version'], [])
+        return [self._get_project_data(projects, p) for p in args['projects']]
+
+    def _get_project_data(self, projects, project):
+        atom = {
+            'project': project,
+            'score': None,
+            'status': None
+        }
+        for p in projects:
+            if p['project'] == project:
+                return p
+        return atom
+
+    def _get_scenarios(self):
+        url = '{}/scenarios'.format(conf.base_url)
+        resp = requests.get(url).json()
+        data = self._change_to_utf8(resp).get('scenarios', {})
+        return {a.get('name'): self._get_scenario(a.get('installers', [])
+                                                  ) for a in data}
+
+    def _get_scenario(self, data):
+        installers = {a.get('installer'): self._get_installer(a.get('versions',
+                                                                    [])
+                                                              ) for a in data}
+        scenario = {
+            'status': self._get_status(),
+            'installers': installers
+        }
+        return scenario
+
+    def _get_status(self):
+        return 'success'
+
+    def _get_installer(self, data):
+        return {a.get('version'): self._get_version(a) for a in data}
+
+    def _get_version(self, data):
+        try:
+            scores = data.get('score', {}).get('projects')[0]
+            trusts = data.get('trust_indicator', {}).get('projects')[0]
+        except (TypeError, IndexError):
+            return []
+        else:
+            scores = {key: [dict(date=a.get('date')[:10],
+                                 score=a.get('score')
+                                 ) for a in scores[key]] for key in scores}
+            trusts = {key: [dict(date=a.get('date')[:10],
+                                 status=a.get('status')
+                                 ) for a in trusts[key]] for key in trusts}
+            atom = self._get_atom(scores, trusts)
+            return [dict(project=k,
+                         score=sorted(atom[k], reverse=True)[0].get('score'),
+                         status=sorted(atom[k], reverse=True)[0].get('status')
+                         ) for k in atom if atom[k]]
+
+    def _get_atom(self, scores, trusts):
+        s = {k: {a['date']: a['score'] for a in scores[k]} for k in scores}
+        t = {k: {a['date']: a['status'] for a in trusts[k]} for k in trusts}
+        return {k: [dict(score=s[k][a], status=t[k][a], data=a
+                         ) for a in s[k] if a in t[k]] for k in s}
+
+    def _change_to_utf8(self, obj):
+        if isinstance(obj, dict):
+            return {str(k): self._change_to_utf8(v) for k, v in obj.items()}
+        elif isinstance(obj, list):
+            return [self._change_to_utf8(ele) for ele in obj]
+        else:
+            try:
+                new = eval(obj)
+                if isinstance(new, int):
+                    return obj
+                return self._change_to_utf8(new)
+            except (NameError, TypeError, SyntaxError):
+                return str(obj)
+
+    def _get_args(self, body):
+        status = self._get_status_args(body)
+        projects = self._get_projects_args(body)
+        installers = self._get_installers_args(body)
+
+        args = {
+            'status': status,
+            'projects': projects,
+            'installers': installers,
+            'version': body.get('version', 'master').lower(),
+            'loops': body.get('loops', 'daily').lower(),
+            'time': body.get('times', '10 days')[:2].lower()
+        }
+        return args
+
+    def _get_status_args(self, body):
+        status_all = ['success', 'warning', 'danger']
+        status = [a.lower() for a in body.get('status', ['all'])]
+        return status_all if 'all' in status else status
+
+    def _get_projects_args(self, body):
+        project_all = ['functest', 'yardstick']
+        projects = [a.lower() for a in body.get('projects', ['all'])]
+        return project_all if 'all' in projects else projects
+
+    def _get_installers_args(self, body):
+        installer_all = ['apex', 'compass', 'fuel', 'joid']
+        installers = [a.lower() for a in body.get('installers', ['all'])]
+        return installer_all if 'all' in installers else installers
diff --git a/utils/test/reporting/api/api/handlers/projects.py b/utils/test/reporting/api/api/handlers/projects.py
new file mode 100644 (file)
index 0000000..02412cd
--- /dev/null
@@ -0,0 +1,27 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apaiche.org/licenses/LICENSE-2.0
+##############################################################################
+import requests
+
+from tornado.escape import json_encode
+
+from api.handlers import BaseHandler
+from api import conf
+
+
+class Projects(BaseHandler):
+    def get(self):
+        self._set_header()
+
+        url = '{}/projects'.format(conf.base_url)
+        projects = requests.get(url).json().get('projects', {})
+
+        project_url = 'https://wiki.opnfv.org/display/{}'
+        data = {p['name']: project_url.format(p['name']) for p in projects}
+
+        return self.write(json_encode(data))
diff --git a/utils/test/reporting/api/api/handlers/testcases.py b/utils/test/reporting/api/api/handlers/testcases.py
new file mode 100644 (file)
index 0000000..110ac4c
--- /dev/null
@@ -0,0 +1,33 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import requests
+
+from tornado.escape import json_encode
+
+from api.handlers import BaseHandler
+from api import conf
+
+
+class TestCases(BaseHandler):
+    def get(self, project):
+        self._set_header()
+
+        url = '{}/projects/{}/cases'.format(conf.base_url, project)
+        cases = requests.get(url).json().get('testcases', [])
+        data = [t['name'] for t in cases]
+        self.write(json_encode(data))
+
+
+class TestCase(BaseHandler):
+    def get(self, project, name):
+        self._set_header()
+
+        url = '{}/projects/{}/cases/{}'.format(conf.base_url, project, name)
+        data = requests.get(url).json()
+        self.write(json_encode(data))
diff --git a/utils/test/reporting/api/api/server.py b/utils/test/reporting/api/api/server.py
new file mode 100644 (file)
index 0000000..e340b01
--- /dev/null
@@ -0,0 +1,27 @@
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import tornado.ioloop
+import tornado.web
+from tornado.options import define
+from tornado.options import options
+
+from api.urls import mappings
+
+define("port", default=8000, help="run on the given port", type=int)
+
+
+def main():
+    tornado.options.parse_command_line()
+    application = tornado.web.Application(mappings)
+    application.listen(options.port)
+    tornado.ioloop.IOLoop.current().start()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/utils/test/reporting/api/api/urls.py b/utils/test/reporting/api/api/urls.py
new file mode 100644 (file)
index 0000000..a5228b2
--- /dev/null
@@ -0,0 +1,20 @@
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from api.handlers import landing
+from api.handlers import projects
+from api.handlers import testcases
+
+mappings = [
+    (r"/landing-page/filters", landing.FiltersHandler),
+    (r"/landing-page/scenarios", landing.ScenariosHandler),
+
+    (r"/projects-page/projects", projects.Projects),
+    (r"/projects/([^/]+)/cases", testcases.TestCases),
+    (r"/projects/([^/]+)/cases/([^/]+)", testcases.TestCase)
+]
diff --git a/utils/test/reporting/api/requirements.txt b/utils/test/reporting/api/requirements.txt
new file mode 100644 (file)
index 0000000..12ad688
--- /dev/null
@@ -0,0 +1,3 @@
+tornado==4.4.2
+requests==2.1.0
+
diff --git a/utils/test/reporting/api/setup.cfg b/utils/test/reporting/api/setup.cfg
new file mode 100644 (file)
index 0000000..53d1092
--- /dev/null
@@ -0,0 +1,32 @@
+[metadata]
+name = reporting
+
+author = JackChan
+author-email = chenjiankun1@huawei.com
+
+classifier =
+    Environment :: opnfv
+    Intended Audience :: Information Technology
+    Intended Audience :: System Administrators
+    License :: OSI Approved :: Apache Software License
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python
+    Programming Language :: Python :: 2
+    Programming Language :: Python :: 2.7
+
+[global]
+setup-hooks =
+    pbr.hooks.setup_hook
+
+[files]
+packages =
+    api
+
+[entry_points]
+console_scripts =
+    api = api.server:main
+
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
diff --git a/utils/test/reporting/api/setup.py b/utils/test/reporting/api/setup.py
new file mode 100644 (file)
index 0000000..d974816
--- /dev/null
@@ -0,0 +1,9 @@
+import setuptools
+
+
+__author__ = 'JackChan'
+
+
+setuptools.setup(
+    setup_requires=['pbr>=1.8'],
+    pbr=True)
diff --git a/utils/test/reporting/docker/Dockerfile b/utils/test/reporting/docker/Dockerfile
new file mode 100644 (file)
index 0000000..ad278ce
--- /dev/null
@@ -0,0 +1,54 @@
+########################################
+#   Docker container for OPNFV-REPORTING
+########################################
+# Purpose: run opnfv-reporting to provide consistent Testing reporting
+#
+# Maintained by Morgan Richomme
+# Build:
+#    $ docker build -t opnfv/testreporting:tag .
+##
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+FROM nginx:stable
+
+MAINTAINER Morgan Richomme <morgan.richomme@orange.com>
+LABEL version="danube.1.0" description="OPNFV Test Reporting Docker container"
+
+ARG BRANCH=master
+
+ENV HOME /home/opnfv
+ENV working_dir /home/opnfv/utils/test/reporting
+ENV TERM xterm
+ENV COLORTERM gnome-terminal
+ENV CONFIG_REPORTING_YAML /home/opnfv/utils/test/reporting/reporting.yaml
+
+# Packaged dependencies
+RUN apt-get update && apt-get install -y \
+ssh \
+python-pip \
+git-core \
+wkhtmltopdf \
+nodejs \
+npm \
+supervisor \
+--no-install-recommends
+
+RUN pip install --upgrade pip
+
+RUN git clone --depth 1 https://gerrit.opnfv.org/gerrit/releng /home/opnfv
+RUN pip install -r ${working_dir}/docker/requirements.pip
+
+WORKDIR ${working_dir}/api
+RUN pip install -r requirements.txt
+RUN python setup.py install
+
+WORKDIR ${working_dir}
+RUN docker/reporting.sh
+
+expose 8000
+
+CMD ["/usr/bin/supervisord"]
diff --git a/utils/test/reporting/docker/nginx.conf b/utils/test/reporting/docker/nginx.conf
new file mode 100644 (file)
index 0000000..9e26972
--- /dev/null
@@ -0,0 +1,24 @@
+upstream backends {
+    server localhost:8001;
+    server localhost:8002;
+    server localhost:8003;
+    server localhost:8004;
+}
+
+
+server {
+    listen 8000;
+    server_name localhost;
+
+    location / {
+        proxy_pass http://backends;
+    }
+
+    location /reporting/ {
+        alias /home/opnfv/utils/test/reporting/pages/dist/;
+    }
+
+    location /display/ {
+        alias /home/opnfv/utils/test/reporting/display/;
+    }
+}
diff --git a/utils/test/reporting/docker/reporting.sh b/utils/test/reporting/docker/reporting.sh
new file mode 100755 (executable)
index 0000000..1de13ae
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+export PYTHONPATH="${PYTHONPATH}:."
+export CONFIG_REPORTING_YAML=./reporting.yaml
+
+declare -a versions=(colorado master)
+declare -a projects=(functest storperf yardstick)
+
+project=$1
+reporting_type=$2
+
+# create Directories if needed
+for i in "${versions[@]}"
+do
+    for j in "${projects[@]}"
+       do
+           mkdir -p display/$i/$j
+       done
+done
+
+# copy images, js, css, 3rd_party
+cp -Rf 3rd_party display
+cp -Rf css display
+cp -Rf html/* display
+cp -Rf img display
+cp -Rf js display
+
+# if nothing is precised run all the reporting generation
+#  projet   |        option
+#   $1      |          $2
+# functest  | status, vims, tempest
+# yardstick |
+# storperf  |
+
+if [ -z "$1" ]; then
+  echo "********************************"
+  echo " Functest reporting "
+  echo "********************************"
+  echo "reporting vIMS..."
+  python ./functest/reporting-vims.py
+  echo "reporting vIMS...OK"
+  sleep 10
+  echo "reporting Tempest..."
+  python ./functest/reporting-tempest.py
+  echo "reporting Tempest...OK"
+  sleep 10
+  echo "reporting status..."
+  python ./functest/reporting-status.py
+  echo "Functest reporting status...OK"
+
+  echo "********************************"
+  echo " Yardstick reporting "
+  echo "********************************"
+  python ./yardstick/reporting-status.py
+  echo "Yardstick reporting status...OK"
+
+  echo "********************************"
+  echo " Storperf reporting "
+  echo "********************************"
+  python ./storperf/reporting-status.py
+  echo "Storperf reporting status...OK"
+
+else
+  if [ -z "$2" ]; then
+    reporting_type="status"
+  fi
+  echo "********************************"
+  echo " $project/$reporting_type reporting "
+  echo "********************************"
+  python ./$project/reporting-$reporting_type.py
+fi
+cp -r display /usr/share/nginx/html
+
+
+# nginx config
+cp /home/opnfv/utils/test/reporting/docker/nginx.conf /etc/nginx/conf.d/
+echo "daemon off;" >> /etc/nginx/nginx.conf
+
+# supervisor config
+cp /home/opnfv/utils/test/reporting/docker/supervisor.conf /etc/supervisor/conf.d/
+
+ln -s /usr/bin/nodejs /usr/bin/node
diff --git a/utils/test/reporting/docker/requirements.pip b/utils/test/reporting/docker/requirements.pip
new file mode 100644 (file)
index 0000000..6de856e
--- /dev/null
@@ -0,0 +1,14 @@
+#
+#
+# author: Morgan Richomme (morgan.richomme@orange.com)
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+pdfkit==0.5.0
+PyYAML==3.11
+simplejson==3.8.1
+jinja2==2.8
+tornado==4.4.2
diff --git a/utils/test/reporting/docker/supervisor.conf b/utils/test/reporting/docker/supervisor.conf
new file mode 100644 (file)
index 0000000..5e315ba
--- /dev/null
@@ -0,0 +1,22 @@
+[supervisord]
+nodaemon = true
+
+[program:reporting_tornado]
+user = root
+directory = /home/opnfv/utils/test/reporting/api/api
+command = python server.py --port=800%(process_num)d
+process_name=%(program_name)s%(process_num)d
+numprocs=4
+numprocs_start=1
+autorestart = true
+
+[program:reporting_nginx]
+user = root
+command = service nginx restart
+autorestart = true
+
+[program:reporting_angular]
+user = root
+directory = /home/opnfv/utils/test/reporting/pages
+command = bash angular.sh
+autorestart = true
index 653448e..af1d1d8 100755 (executable)
 #
 import datetime
 import jinja2
-import pdfkit
+import os
 import requests
 import sys
 import time
 import yaml
 
-import reportingUtils as utils
-import reportingConf as conf
 import testCase as tc
 import scenarioResult as sr
 
+# manage conf
+import utils.reporting_utils as rp_utils
+
 # Logger
-logger = utils.getLogger("Status")
+logger = rp_utils.getLogger("Functest-Status")
 
 # Initialization
 testValid = []
 otherTestCases = []
 reportingDate = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
 
-# init just tempest to get the list of scenarios
-# as all the scenarios run Tempest
-tempest = tc.TestCase("tempest_smoke_serial", "functest", -1)
+# init just connection_check to get the list of scenarios
+# as all the scenarios run connection_check
+healthcheck = tc.TestCase("connection_check", "functest", -1)
 
 # Retrieve the Functest configuration to detect which tests are relevant
 # according to the installer, scenario
-cf = conf.TEST_CONF
+cf = rp_utils.get_config('functest.test_conf')
+period = rp_utils.get_config('general.period')
+versions = rp_utils.get_config('general.versions')
+installers = rp_utils.get_config('general.installers')
+blacklist = rp_utils.get_config('functest.blacklist')
+log_level = rp_utils.get_config('general.log.log_level')
+exclude_noha = rp_utils.get_config('functest.exclude_noha')
+exclude_virtual = rp_utils.get_config('functest.exclude_virtual')
+
 response = requests.get(cf)
 
 functest_yaml_config = yaml.safe_load(response.text)
 
 logger.info("*******************************************")
+logger.info("*                                         *")
 logger.info("*   Generating reporting scenario status  *")
-logger.info("*   Data retention = %s days              *" % conf.PERIOD)
+logger.info("*   Data retention: %s days               *" % period)
+logger.info("*   Log level: %s                         *" % log_level)
+logger.info("*                                         *")
+logger.info("*   Virtual PODs exluded: %s              *" % exclude_virtual)
+logger.info("*   NOHA scenarios excluded: %s           *" % exclude_noha)
 logger.info("*                                         *")
 logger.info("*******************************************")
 
 # Retrieve test cases of Tier 1 (smoke)
 config_tiers = functest_yaml_config.get("tiers")
 
-# we consider Tier 1 (smoke),2 (sdn suites) and 3 (features)
+# we consider Tier 0 (Healthcheck), Tier 1 (smoke),2 (features)
 # to validate scenarios
-# Tier > 4 are not used to validate scenarios but we display the results anyway
+# Tier > 2 are not used to validate scenarios but we display the results anyway
 # tricky thing for the API as some tests are Functest tests
 # other tests are declared directly in the feature projects
 for tier in config_tiers:
-    if tier['order'] > 0 and tier['order'] < 3:
+    if tier['order'] >= 0 and tier['order'] < 2:
         for case in tier['testcases']:
-            if case['name'] not in conf.blacklist:
+            if case['name'] not in blacklist:
                 testValid.append(tc.TestCase(case['name'],
                                              "functest",
                                              case['dependencies']))
-    elif tier['order'] == 3:
+    elif tier['order'] == 2:
         for case in tier['testcases']:
-            if case['name'] not in conf.blacklist:
+            if case['name'] not in blacklist:
                 testValid.append(tc.TestCase(case['name'],
                                              case['name'],
                                              case['dependencies']))
-    elif tier['order'] > 3:
+    elif tier['order'] > 2:
         for case in tier['testcases']:
-            if case['name'] not in conf.blacklist:
+            if case['name'] not in blacklist:
                 otherTestCases.append(tc.TestCase(case['name'],
                                                   "functest",
                                                   case['dependencies']))
 
+logger.debug("Functest reporting start")
 # For all the versions
-for version in conf.versions:
+for version in versions:
     # For all the installers
-    for installer in conf.installers:
+    for installer in installers:
         # get scenarios
-        scenario_results = utils.getScenarios(tempest, installer, version)
-        scenario_stats = utils.getScenarioStats(scenario_results)
+        scenario_results = rp_utils.getScenarios(healthcheck,
+                                                 installer,
+                                                 version)
+        scenario_stats = rp_utils.getScenarioStats(scenario_results)
         items = {}
         scenario_result_criteria = {}
+        scenario_directory = "./display/" + version + "/functest/"
+        scenario_file_name = scenario_directory + "scenario_history.txt"
 
-        scenario_file_name = (conf.REPORTING_PATH +
-                              "/functest/release/" + version +
-                              "/scenario_history.txt")
+        # check that the directory exists, if not create it
+        # (first run on new version)
+        if not os.path.exists(scenario_directory):
+            os.makedirs(scenario_directory)
+
+        # initiate scenario file if it does not exist
+        if not os.path.isfile(scenario_file_name):
+            with open(scenario_file_name, "a") as my_file:
+                logger.debug("Create scenario file: %s" % scenario_file_name)
+                my_file.write("date,scenario,installer,detail,score\n")
 
         # For all the scenarios get results
         for s, s_result in scenario_results.items():
@@ -102,7 +128,9 @@ for version in conf.versions:
             if len(s_result) > 0:
                 build_tag = s_result[len(s_result)-1]['build_tag']
                 logger.debug("Build tag: %s" % build_tag)
-                s_url = s_url = utils.getJenkinsUrl(build_tag)
+                s_url = rp_utils.getJenkinsUrl(build_tag)
+                if s_url is None:
+                    s_url = "http://testresultS.opnfv.org/reporting"
                 logger.info("last jenkins url: %s" % s_url)
             testCases2BeDisplayed = []
             # Check if test case is runnable / installer, scenario
@@ -126,7 +154,8 @@ for version in conf.versions:
                         nb_test_runnable_for_this_scenario += 1
                         logger.info(" Searching results for case %s " %
                                     (displayName))
-                        result = utils.getResult(dbName, installer, s, version)
+                        result = rp_utils.getResult(dbName, installer,
+                                                    s, version)
                         # if no result set the value to 0
                         if result < 0:
                             result = 0
@@ -158,7 +187,8 @@ for version in conf.versions:
                         project = test_case.getProject()
                         logger.info(" Searching results for case %s " %
                                     (displayName))
-                        result = utils.getResult(dbName, installer, s, version)
+                        result = rp_utils.getResult(dbName, installer,
+                                                    s, version)
                         # at least 1 result for the test
                         if result > -1:
                             test_case.setCriteria(result)
@@ -186,11 +216,11 @@ for version in conf.versions:
             scenario_criteria = nb_test_runnable_for_this_scenario * 3
             # if 0 runnable tests set criteria at a high value
             if scenario_criteria < 1:
-                scenario_criteria = conf.MAX_SCENARIO_CRITERIA
+                scenario_criteria = 50  # conf.MAX_SCENARIO_CRITERIA
 
             s_score = str(scenario_score) + "/" + str(scenario_criteria)
-            s_score_percent = utils.getScenarioPercent(scenario_score,
-                                                       scenario_criteria)
+            s_score_percent = rp_utils.getScenarioPercent(scenario_score,
+                                                          scenario_criteria)
 
             s_status = "KO"
             if scenario_score < scenario_criteria:
@@ -200,9 +230,9 @@ for version in conf.versions:
             else:
                 logger.info(">>>>> scenario OK, save the information")
                 s_status = "OK"
-                path_validation_file = (conf.REPORTING_PATH +
-                                        "/functest/release/" + version +
-                                        "/validated_scenario_history.txt")
+                path_validation_file = ("./display/" + version +
+                                        "/functest/" +
+                                        "validated_scenario_history.txt")
                 with open(path_validation_file, "a") as f:
                     time_format = "%Y-%m-%d %H:%M"
                     info = (datetime.datetime.now().strftime(time_format) +
@@ -222,54 +252,37 @@ for version in conf.versions:
                                                             s_url)
             logger.info("--------------------------")
 
-        templateLoader = jinja2.FileSystemLoader(conf.REPORTING_PATH)
+        templateLoader = jinja2.FileSystemLoader(".")
         templateEnv = jinja2.Environment(
             loader=templateLoader, autoescape=True)
 
-        TEMPLATE_FILE = "/functest/template/index-status-tmpl.html"
+        TEMPLATE_FILE = "./functest/template/index-status-tmpl.html"
         template = templateEnv.get_template(TEMPLATE_FILE)
 
         outputText = template.render(scenario_stats=scenario_stats,
                                      scenario_results=scenario_result_criteria,
                                      items=items,
                                      installer=installer,
-                                     period=conf.PERIOD,
+                                     period=period,
                                      version=version,
                                      date=reportingDate)
 
-        # csv
-        # generate sub files based on scenario_history.txt
-        scenario_installer_file_name = (conf.REPORTING_PATH +
-                                        "/functest/release/" + version +
-                                        "/scenario_history_" + installer +
-                                        ".txt")
-        scenario_installer_file = open(scenario_installer_file_name, "a")
-        logger.info("Generate CSV...")
-        with open(scenario_file_name, "r") as f:
-            for line in f:
-                if installer in line:
-                    logger.debug("Add new line... %s" % line)
-                    scenario_installer_file.write(line)
-        scenario_installer_file.close
-
-        with open(conf.REPORTING_PATH + "/functest/release/" + version +
-                  "/index-status-" + installer + ".html", "wb") as fh:
+        with open("./display/" + version +
+                  "/functest/status-" + installer + ".html", "wb") as fh:
             fh.write(outputText)
-        logger.info("CSV generated...")
+
+        logger.info("Manage export CSV & PDF")
+        rp_utils.export_csv(scenario_file_name, installer, version)
+        logger.error("CSV generated...")
 
         # Generate outputs for export
         # pdf
-        logger.info("Generate PDF...")
-        try:
-            pdf_path = ("http://testresults.opnfv.org/reporting/" +
-                        "functest/release/" + version +
-                        "/index-status-" + installer + ".html")
-            pdf_doc_name = (conf.REPORTING_PATH +
-                            "/functest/release/" + version +
-                            "/status-" + installer + ".pdf")
-            pdfkit.from_url(pdf_path, pdf_doc_name)
-            logger.info("PDF generated...")
-        except IOError:
-            logger.info("pdf generated anyway...")
-        except:
-            logger.error("impossible to generate PDF")
+        # TODO Change once web site updated...use the current one
+        # to test pdf production
+        url_pdf = rp_utils.get_config('general.url')
+        pdf_path = ("./display/" + version +
+                    "/functest/status-" + installer + ".html")
+        pdf_doc_name = ("./display/" + version +
+                        "/functest/status-" + installer + ".pdf")
+        rp_utils.export_pdf(pdf_path, pdf_doc_name)
+        logger.info("PDF generated...")
index 363f123..6e6585a 100755 (executable)
@@ -1,18 +1,22 @@
 from urllib2 import Request, urlopen, URLError
 import json
 import jinja2
-import reportingConf as conf
-import reportingUtils as utils
+import os
 
-installers = conf.installers
+# manage conf
+import utils.reporting_utils as rp_utils
+
+installers = rp_utils.get_config('general.installers')
 items = ["tests", "Success rate", "duration"]
 
-PERIOD = conf.PERIOD
+CURRENT_DIR = os.getcwd()
+
+PERIOD = rp_utils.get_config('general.period')
 criteria_nb_test = 165
 criteria_duration = 1800
 criteria_success_rate = 90
 
-logger = utils.getLogger("Tempest")
+logger = rp_utils.getLogger("Tempest")
 logger.info("************************************************")
 logger.info("*   Generating reporting Tempest_smoke_serial  *")
 logger.info("*   Data retention = %s days                   *" % PERIOD)
@@ -25,10 +29,11 @@ logger.info("test duration < %s s " % criteria_duration)
 logger.info("success rate > %s " % criteria_success_rate)
 
 # For all the versions
-for version in conf.versions:
-    for installer in conf.installers:
+for version in rp_utils.get_config('general.versions'):
+    for installer in installers:
         # we consider the Tempest results of the last PERIOD days
-        url = 'http://' + conf.URL_BASE + "?case=tempest_smoke_serial"
+        url = ("http://" + rp_utils.get_config('testapi.url') +
+               "?case=tempest_smoke_serial")
         request = Request(url + '&period=' + str(PERIOD) +
                           '&installer=' + installer +
                           '&version=' + version)
@@ -39,7 +44,7 @@ for version in conf.versions:
             response = urlopen(request)
             k = response.read()
             results = json.loads(k)
-        except URLError, e:
+        except URLError as e:
             logger.error("Error code: %s" % e)
 
         test_results = results['results']
@@ -68,8 +73,9 @@ for version in conf.versions:
                 nb_tests_run = result['details']['tests']
                 nb_tests_failed = result['details']['failures']
                 if nb_tests_run != 0:
-                    success_rate = 100*(int(nb_tests_run) -
-                                        int(nb_tests_failed)) / int(nb_tests_run)
+                    success_rate = 100 * ((int(nb_tests_run) -
+                                           int(nb_tests_failed)) /
+                                          int(nb_tests_run))
                 else:
                     success_rate = 0
 
@@ -115,17 +121,18 @@ for version in conf.versions:
                 except:
                     logger.error("Error field not present (Brahamputra runs?)")
 
-        templateLoader = jinja2.FileSystemLoader(conf.REPORTING_PATH)
-        templateEnv = jinja2.Environment(loader=templateLoader, autoescape=True)
+        templateLoader = jinja2.FileSystemLoader(".")
+        templateEnv = jinja2.Environment(loader=templateLoader,
+                                         autoescape=True)
 
-        TEMPLATE_FILE = "/template/index-tempest-tmpl.html"
+        TEMPLATE_FILE = "./functest/template/index-tempest-tmpl.html"
         template = templateEnv.get_template(TEMPLATE_FILE)
 
         outputText = template.render(scenario_results=scenario_results,
                                      items=items,
                                      installer=installer)
 
-        with open(conf.REPORTING_PATH + "/release/" + version +
-                  "/index-tempest-" + installer + ".html", "wb") as fh:
+        with open("./display/" + version +
+                  "/functest/tempest-" + installer + ".html", "wb") as fh:
             fh.write(outputText)
 logger.info("Tempest automatic reporting succesfully generated.")
index 430a545..b236b89 100755 (executable)
@@ -1,10 +1,11 @@
 from urllib2 import Request, urlopen, URLError
 import json
 import jinja2
-import reportingConf as conf
-import reportingUtils as utils
 
-logger = utils.getLogger("vIMS")
+# manage conf
+import utils.reporting_utils as rp_utils
+
+logger = rp_utils.getLogger("vIMS")
 
 
 def sig_test_format(sig_test):
@@ -24,29 +25,33 @@ def sig_test_format(sig_test):
     total_sig_test_result['skipped'] = nbSkipped
     return total_sig_test_result
 
+period = rp_utils.get_config('general.period')
+versions = rp_utils.get_config('general.versions')
+url_base = rp_utils.get_config('testapi.url')
+
 logger.info("****************************************")
 logger.info("*   Generating reporting vIMS          *")
-logger.info("*   Data retention = %s days           *" % conf.PERIOD)
+logger.info("*   Data retention = %s days           *" % period)
 logger.info("*                                      *")
 logger.info("****************************************")
 
-installers = conf.installers
+installers = rp_utils.get_config('general.installers')
 step_order = ["initialisation", "orchestrator", "vIMS", "sig_test"]
 logger.info("Start processing....")
 
 # For all the versions
-for version in conf.versions:
+for version in versions:
     for installer in installers:
         logger.info("Search vIMS results for installer: %s, version: %s"
                     % (installer, version))
-        request = Request("http://" + conf.URL_BASE + '?case=vims&installer=' +
+        request = Request("http://" + url_base + '?case=vims&installer=' +
                           installer + '&version=' + version)
 
         try:
             response = urlopen(request)
             k = response.read()
             results = json.loads(k)
-        except URLError, e:
+        except URLError as e:
             logger.error("Error code: %s" % e)
 
         test_results = results['results']
@@ -81,16 +86,18 @@ for version in conf.versions:
                     if int(m) != 0:
                         m_display += str(int(m)) + "m "
 
-                    step_result['duration_display'] = m_display + str(int(s)) + "s"
+                    step_result['duration_display'] = (m_display +
+                                                       str(int(s)) + "s")
 
                 result['pr_step_ok'] = 0
                 if nb_step != 0:
-                    result['pr_step_ok'] = (float(nb_step_ok)/nb_step)*100
+                    result['pr_step_ok'] = (float(nb_step_ok) / nb_step) * 100
                 try:
                     logger.debug("Scenario %s, Installer %s"
                                  % (s_result[1]['scenario'], installer))
+                    res = result['details']['orchestrator']['duration']
                     logger.debug("Orchestrator deployment: %s s"
-                                 % result['details']['orchestrator']['duration'])
+                                 % res)
                     logger.debug("vIMS deployment: %s s"
                                  % result['details']['vIMS']['duration'])
                     logger.debug("Signaling testing: %s s"
@@ -101,18 +108,18 @@ for version in conf.versions:
                     logger.error("Data badly formatted")
                 logger.debug("----------------------------------------")
 
-        templateLoader = jinja2.FileSystemLoader(conf.REPORTING_PATH)
-        templateEnv = jinja2.Environment(loader=templateLoader, autoescape=True)
+        templateLoader = jinja2.FileSystemLoader(".")
+        templateEnv = jinja2.Environment(loader=templateLoader,
+                                         autoescape=True)
 
-        TEMPLATE_FILE = "/template/index-vims-tmpl.html"
+        TEMPLATE_FILE = "./functest/template/index-vims-tmpl.html"
         template = templateEnv.get_template(TEMPLATE_FILE)
 
         outputText = template.render(scenario_results=scenario_results,
                                      step_order=step_order,
                                      installer=installer)
 
-        with open(conf.REPORTING_PATH +
-                  "/release/" + version + "/index-vims-" +
+        with open("./display/" + version + "/functest/vims-" +
                   installer + ".html", "wb") as fh:
             fh.write(outputText)
 
diff --git a/utils/test/reporting/functest/reportingConf.py b/utils/test/reporting/functest/reportingConf.py
deleted file mode 100644 (file)
index 1c9a2ac..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/python
-#
-# This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Reporting: Declaration of the variables
-#
-# ****************************************************
-installers = ["apex", "compass", "fuel", "joid"]
-# list of test cases declared in testcases.yaml but that must not be
-# taken into account for the scoring
-blacklist = ["ovno", "security_scan"]
-versions = ["master", "colorado"]
-PERIOD = 10
-MAX_SCENARIO_CRITERIA = 50
-# get the last 5 test results to determinate the success criteria
-NB_TESTS = 5
-# REPORTING_PATH = "/usr/share/nginx/html/reporting/functest"
-REPORTING_PATH = "."
-URL_BASE = 'testresults.opnfv.org/test/api/v1/results'
-TEST_CONF = "https://git.opnfv.org/cgit/functest/plain/ci/testcases.yaml"
-LOG_LEVEL = "ERROR"
-LOG_FILE = REPORTING_PATH + "/reporting.log"
diff --git a/utils/test/reporting/functest/reportingUtils.py b/utils/test/reporting/functest/reportingUtils.py
deleted file mode 100644 (file)
index 74d6f19..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/usr/bin/python
-#
-# This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-from urllib2 import Request, urlopen, URLError
-import logging
-import json
-import reportingConf as conf
-
-
-def getLogger(module):
-    logFormatter = logging.Formatter("%(asctime)s [" +
-                                     module +
-                                     "] [%(levelname)-5.5s]  %(message)s")
-    logger = logging.getLogger()
-
-    fileHandler = logging.FileHandler("{0}/{1}".format('.', conf.LOG_FILE))
-    fileHandler.setFormatter(logFormatter)
-    logger.addHandler(fileHandler)
-
-    consoleHandler = logging.StreamHandler()
-    consoleHandler.setFormatter(logFormatter)
-    logger.addHandler(consoleHandler)
-    logger.setLevel(conf.LOG_LEVEL)
-    return logger
-
-
-def getApiResults(case, installer, scenario, version):
-    results = json.dumps([])
-    # to remove proxy (to be removed at the end for local test only)
-    # proxy_handler = urllib2.ProxyHandler({})
-    # opener = urllib2.build_opener(proxy_handler)
-    # urllib2.install_opener(opener)
-    # url = "http://127.0.0.1:8000/results?case=" + case + \
-    #       "&period=30&installer=" + installer
-    url = ("http://" + conf.URL_BASE + "?case=" + case +
-           "&period=" + str(conf.PERIOD) + "&installer=" + installer +
-           "&scenario=" + scenario + "&version=" + version +
-           "&last=" + str(conf.NB_TESTS))
-    request = Request(url)
-
-    try:
-        response = urlopen(request)
-        k = response.read()
-        results = json.loads(k)
-    except URLError, e:
-        print 'No kittez. Got an error code:', e
-
-    return results
-
-
-def getScenarios(case, installer, version):
-
-    case = case.getName()
-    url = ("http://" + conf.URL_BASE + "?case=" + case +
-           "&period=" + str(conf.PERIOD) + "&installer=" + installer +
-           "&version=" + version)
-    request = Request(url)
-
-    try:
-        response = urlopen(request)
-        k = response.read()
-        results = json.loads(k)
-        test_results = results['results']
-    except URLError, e:
-        print 'Got an error code:', e
-
-    if test_results is not None:
-        test_results.reverse()
-
-        scenario_results = {}
-
-        for r in test_results:
-            # Retrieve all the scenarios per installer
-            if not r['scenario'] in scenario_results.keys():
-                scenario_results[r['scenario']] = []
-            scenario_results[r['scenario']].append(r)
-
-    return scenario_results
-
-
-def getScenarioStats(scenario_results):
-    scenario_stats = {}
-    for k, v in scenario_results.iteritems():
-        scenario_stats[k] = len(v)
-
-    return scenario_stats
-
-
-def getNbtestOk(results):
-    nb_test_ok = 0
-    for r in results:
-        for k, v in r.iteritems():
-            try:
-                if "PASS" in v:
-                    nb_test_ok += 1
-            except:
-                print "Cannot retrieve test status"
-    return nb_test_ok
-
-
-def getResult(testCase, installer, scenario, version):
-
-    # retrieve raw results
-    results = getApiResults(testCase, installer, scenario, version)
-    # let's concentrate on test results only
-    test_results = results['results']
-
-    # if results found, analyze them
-    if test_results is not None:
-        test_results.reverse()
-
-        scenario_results = []
-
-        # print " ---------------- "
-        # print test_results
-        # print " ---------------- "
-        # print "nb of results:" + str(len(test_results))
-
-        for r in test_results:
-            # print r["start_date"]
-            # print r["criteria"]
-            scenario_results.append({r["start_date"]: r["criteria"]})
-        # sort results
-        scenario_results.sort()
-        # 4 levels for the results
-        # 3: 4+ consecutive runs passing the success criteria
-        # 2: <4 successful consecutive runs but passing the criteria
-        # 1: close to pass the success criteria
-        # 0: 0% success, not passing
-        # -1: no run available
-        test_result_indicator = 0
-        nbTestOk = getNbtestOk(scenario_results)
-
-        # print "Nb test OK (last 10 days):"+ str(nbTestOk)
-        # check that we have at least 4 runs
-        if len(scenario_results) < 1:
-            # No results available
-            test_result_indicator = -1
-        elif nbTestOk < 1:
-            test_result_indicator = 0
-        elif nbTestOk < 2:
-            test_result_indicator = 1
-        else:
-            # Test the last 4 run
-            if (len(scenario_results) > 3):
-                last4runResults = scenario_results[-4:]
-                nbTestOkLast4 = getNbtestOk(last4runResults)
-                # print "Nb test OK (last 4 run):"+ str(nbTestOkLast4)
-                if nbTestOkLast4 > 3:
-                    test_result_indicator = 3
-                else:
-                    test_result_indicator = 2
-            else:
-                test_result_indicator = 2
-    return test_result_indicator
-
-
-def getJenkinsUrl(build_tag):
-    # e.g. jenkins-functest-apex-apex-daily-colorado-daily-colorado-246
-    # id = 246
-    # note it is linked to jenkins format
-    # if this format changes...function to be adapted....
-    url_base = "https://build.opnfv.org/ci/view/functest/job/"
-    jenkins_url = ""
-    try:
-        build_id = [int(s) for s in build_tag.split("-") if s.isdigit()]
-        jenkins_path = filter(lambda c: not c.isdigit(), build_tag)
-        url_id = jenkins_path[8:-1] + "/" + str(build_id[0])
-        jenkins_url = url_base + url_id + "/console"
-    except:
-        print 'Impossible to get jenkins url:'
-
-    return jenkins_url
-
-def getScenarioPercent(scenario_score,scenario_criteria):
-    score = 0.0
-    try:
-        score = float(scenario_score) / float(scenario_criteria) * 100
-    except:
-        print 'Impossible to calculate the percentage score'
-    return score
index e3c9c5f..52046c3 100644 (file)
@@ -3,12 +3,12 @@
     <meta charset="utf-8">
     <!-- Bootstrap core CSS -->
     <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
-    <link href="../../../css/default.css" rel="stylesheet">
+    <link href="../../css/default.css" rel="stylesheet">
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
     <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
     <script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
-    <script type="text/javascript" src="../../../js/gauge.js"></script>
-    <script type="text/javascript" src="../../../js/trend.js"></script>
+    <script type="text/javascript" src="../../js/gauge.js"></script>
+    <script type="text/javascript" src="../../js/trend.js"></script>
     <script>
     function onDocumentReady() {
        // Gauge management
@@ -26,7 +26,7 @@
         }
         
         // trend line management
-        d3.csv("./scenario_history.txt", function(data) {
+        d3.csv("./scenario_history.csv", function(data) {
        // ***************************************
        // Create the trend line
       {% for scenario,iteration in scenario_stats.iteritems() -%}
@@ -69,11 +69,12 @@ $(document).ready(function (){
         <h3 class="text-muted">Functest status page ({{version}}, {{date}})</h3>
         <nav>
           <ul class="nav nav-justified">
-            <li class="active"><a href="http://testresults.opnfv.org/reporting/index.html">Home</a></li>
-            <li><a href="index-status-apex.html">Apex</a></li>
-            <li><a href="index-status-compass.html">Compass</a></li>
-            <li><a href="index-status-fuel.html">Fuel</a></li>
-            <li><a href="index-status-joid.html">Joid</a></li>
+            <li class="active"><a href="../../index.html">Home</a></li>
+            <li><a href="status-apex.html">Apex</a></li>
+            <li><a href="status-compass.html">Compass</a></li>
+            <li><a href="status-daisy.html">Daisy</a></li>
+            <li><a href="status-fuel.html">Fuel</a></li>
+            <li><a href="status-joid.html">Joid</a></li>
           </ul>
         </nav>
       </div>
@@ -133,13 +134,13 @@ $(document).ready(function (){
                         <tr class="tr-weather-weather">
                             {% for test in items[scenario] -%}
                             {% if test.getCriteria() > 2 -%}
-                                <td><img src="../../img/weather-clear.png"></td>
+                                <td><img src="../../../img/weather-clear.png"></td>
                             {%- elif test.getCriteria() > 1 -%}
-                                <td><img src="../../img/weather-few-clouds.png"></td>
+                                <td><img src="../../../img/weather-few-clouds.png"></td>
                             {%- elif test.getCriteria() > 0 -%}
-                                <td><img src="../../img/weather-overcast.png"></td>
+                                <td><img src="../../../img/weather-overcast.png"></td>
                             {%- elif test.getCriteria() > -1 -%}
-                                <td><img src="../../img/weather-storm.png"></td>
+                                <td><img src="../../../img/weather-storm.png"></td>
                             {%- endif %}
                             {%- endfor %}
                         </tr>
index 42d7ed3..3a22227 100644 (file)
@@ -3,7 +3,7 @@
     <meta charset="utf-8">
     <!-- Bootstrap core CSS -->
     <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
-    <link href="default.css" rel="stylesheet">
+    <link href="../../css/default.css" rel="stylesheet">
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
     <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
     <script type="text/javascript">
         <h3 class="text-muted">Tempest status page</h3>
         <nav>
           <ul class="nav nav-justified">
-            <li class="active"><a href="http://testresults.opnfv.org/reporting/index.html">Home</a></li>
-            <li><a href="index-tempest-apex.html">Apex</a></li>
-            <li><a href="index-tempest-compass.html">Compass</a></li>
-            <li><a href="index-tempest-fuel.html">Fuel</a></li>
-            <li><a href="index-tempest-joid.html">Joid</a></li>
+            <li class="active"><a href="../../index.html">Home</a></li>
+            <li><a href="tempest-apex.html">Apex</a></li>
+            <li><a href="tempest-compass.html">Compass</a></li>
+            <li><a href="tempest-daisy.html">Daisy</a></li>
+            <li><a href="tempest-fuel.html">Fuel</a></li>
+            <li><a href="tempest-joid.html">Joid</a></li>
           </ul>
         </nav>
       </div>
index 3836be9..cd51607 100644 (file)
@@ -3,7 +3,7 @@
     <meta charset="utf-8">
     <!-- Bootstrap core CSS -->
     <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
-    <link href="default.css" rel="stylesheet">
+    <link href="../../css/default.css" rel="stylesheet">
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
     <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
     <script type="text/javascript">
         <h3 class="text-muted">vIMS status page</h3>
         <nav>
           <ul class="nav nav-justified">
-            <li class="active"><a href="http://testresults.opnfv.org/reporting/index.html">Home</a></li>
-            <li><a href="index-vims-fuel.html">Fuel</a></li>
-            <li><a href="index-vims-compass.html">Compass</a></li>
-            <li><a href="index-vims-joid.html">JOID</a></li>
-            <li><a href="index-vims-apex.html">APEX</a></li>
+            <li class="active"><a href="../../index.html">Home</a></li>
+            <li><a href="vims-fuel.html">Fuel</a></li>
+            <li><a href="vims-compass.html">Compass</a></li>
+            <li><a href="vims-daisy.html">Daisy</a></li>
+            <li><a href="vims-joid.html">JOID</a></li>
+            <li><a href="vims-apex.html">APEX</a></li>
           </ul>
         </nav>
       </div>
index a906f0d..c89e619 100644 (file)
@@ -27,21 +27,33 @@ class TestCase(object):
                                'ocl': 'OCL',
                                'tempest_smoke_serial': 'Tempest (smoke)',
                                'tempest_full_parallel': 'Tempest (full)',
+                               'tempest_defcore': 'Tempest (Defcore)',
+                               'refstack_defcore': 'Refstack',
                                'rally_sanity': 'Rally (smoke)',
                                'bgpvpn': 'bgpvpn',
                                'rally_full': 'Rally (full)',
                                'vims': 'vIMS',
                                'doctor': 'Doctor',
                                'promise': 'Promise',
-                               'moon': 'moon',
-                               'copper': 'copper',
-                               'security_scan': 'security',
-                               'multisite': 'multisite',
-                               'domino': 'domino',
+                               'moon': 'Moon',
+                               'copper': 'Copper',
+                               'security_scan': 'Security',
+                               'multisite': 'Multisite',
+                               'domino': 'Domino',
                                'odl-sfc': 'SFC',
                                'onos_sfc': 'SFC',
-                               'parser':'parser'
-                               }
+                               'parser': 'Parser',
+                               'connection_check': 'Health (connection)',
+                               'api_check': 'Health (api)',
+                               'snaps_smoke': 'SNAPS',
+                               'snaps_health_check': 'Health (dhcp)',
+                               'netready': 'Netready',
+                               'fds': 'FDS',
+                               'cloudify_ims': 'vIMS (Cloudify)',
+                               'orchestra_ims': 'OpenIMS (OpenBaton)',
+                               'opera_ims': 'vIMS (Open-O)',
+                               'vyos_vrouter': 'vyos',
+                               'barometer': 'Barometer'}
         try:
             self.displayName = display_name_matrix[self.name]
         except:
@@ -120,21 +132,33 @@ class TestCase(object):
                              'ocl': 'ocl',
                              'tempest_smoke_serial': 'tempest_smoke_serial',
                              'tempest_full_parallel': 'tempest_full_parallel',
+                             'tempest_defcore': 'tempest_defcore',
+                             'refstack_defcore': 'refstack_defcore',
                              'rally_sanity': 'rally_sanity',
                              'bgpvpn': 'bgpvpn',
                              'rally_full': 'rally_full',
                              'vims': 'vims',
                              'doctor': 'doctor-notification',
                              'promise': 'promise',
-                             'moon': 'moon',
+                             'moon': 'moon_authentication',
                              'copper': 'copper-notification',
                              'security_scan': 'security',
                              'multisite': 'multisite',
                              'domino': 'domino-multinode',
-                             'odl-sfc': 'odl-sfc',
+                             'odl-sfc': 'functest-odl-sfc',
                              'onos_sfc': 'onos_sfc',
-                             'parser':'parser-basics'
-                             }
+                             'parser': 'parser-basics',
+                             'connection_check': 'connection_check',
+                             'api_check': 'api_check',
+                             'snaps_smoke': 'snaps_smoke',
+                             'snaps_health_check': 'snaps_health_check',
+                             'netready': 'gluon_vping',
+                             'fds': 'fds',
+                             'cloudify_ims': 'cloudify_ims',
+                             'orchestra_ims': 'orchestra_ims',
+                             'opera_ims': 'opera_ims',
+                             'vyos_vrouter': 'vyos_vrouter',
+                             'barometer': 'barometercollectd'}
         try:
             return test_match_matrix[self.name]
         except:
similarity index 72%
rename from utils/test/reporting/colorado.html
rename to utils/test/reporting/html/colorado.html
index 6d66939..58cb009 100644 (file)
@@ -9,10 +9,10 @@
                <title>Phantom by HTML5 UP</title>\r
                <meta charset="utf-8" />\r
                <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->\r
+               <link rel="stylesheet" href="3rd_party/css/main.css" />\r
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->\r
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->\r
        </head>\r
        <body>\r
                <!-- Wrapper -->\r
@@ -24,7 +24,7 @@
 \r
                                                        <!-- Logo -->\r
                                                                <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
                                                                </a>\r
 \r
                                                        <!-- Nav -->\r
@@ -56,7 +56,7 @@
                                                        <section class="tiles">\r
                                                                <article class="style3">\r
                                                                        <span class="image">\r
-                                                                               <img src="images/functest.jpg" alt="" />\r
+                                                                               <img src="img/projectIcon_functest_250x250.png" alt="" />\r
                                                                        </span>\r
                                                                        <a href="functest-colorado.html">\r
                                                                                <h2>Functest</h2>\r
@@ -67,9 +67,9 @@
                                                                </article>\r
                                                                <article class="style2">\r
                                                                        <span class="image">\r
-                                                                               <img src="images/yardstick.jpg" alt="" />\r
+                                                                               <img src="img/projectIcon_yardstick_250x250.png" alt="" />\r
                                                                        </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/yardstick/release/colorado/index-status-apex.html">\r
+                                                                       <a href="colorado/yardstick/status-apex.html">\r
                                                                                <h2>Yardstick</h2>\r
                                                                                <div class="content">\r
                                                                                        <p>Qualification and performance testing</p>\r
                        </div>\r
 \r
                <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
+                       <script src="3rd_party/js/jquery.min.js"></script>\r
+                       <script src="3rd_party/js/skel.min.js"></script>\r
+                       <script src="3rd_party/js/util.js"></script>\r
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->\r
+                       <script src="3rd_party/js/main.js"></script>\r
 \r
        </body>\r
 </html>\r
diff --git a/utils/test/reporting/html/danube.html b/utils/test/reporting/html/danube.html
new file mode 100644 (file)
index 0000000..09de789
--- /dev/null
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<!--
+       Phantom by HTML5 UP
+       html5up.net | @ajlkn
+       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
+-->
+<html>
+       <head>
+               <title>Phantom by HTML5 UP</title>
+               <meta charset="utf-8" />
+               <meta name="viewport" content="width=device-width, initial-scale=1" />
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->
+               <link rel="stylesheet" href="3rd_party/css/main.css" />
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->
+       </head>
+       <body>
+               <!-- Wrapper -->
+                       <div id="wrapper">
+
+                               <!-- Header -->
+                                       <header id="header">
+                                               <div class="inner">
+
+                                                       <!-- Logo -->
+                                                               <a href="index.html" class="logo">
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>
+                                                               </a>
+
+                                                       <!-- Nav -->
+                                                       <!--    <nav>
+                                                                       <ul>
+                                                                               <li><a href="#menu">Menu</a></li>
+                                                                       </ul>
+                                                               </nav>
+                             --->
+                                               </div>
+                                       </header>
+
+                               <!-- Menu -->
+                               <!---   <nav id="menu">
+                                               <h2>Menu</h2>
+                                               <ul>
+                                                       <li><a href="index.html">Home</a></li>
+                                                       <li><a href="colorado.html">Colorado</a></li>
+                                                       <li><a href="danube.html">Danube</a></li>
+                                               </ul>
+                                       </nav>
+                --->
+                               <!-- Main -->
+                                       <div id="main">
+                                               <div class="inner">
+                                                       <header>
+                                                               <h1>Danube reporting</h1>
+                                                       </header>
+                                                       <section class="tiles">
+                                                               <article class="style3">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_functest_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="functest-danube.html">
+                                                                               <h2>Functest</h2>
+                                                                               <div class="content">
+                                                                                       <p>Functional testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style2">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_yardstick_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="danube/yardstick/status-apex.html">
+                                                                               <h2>Yardstick</h2>
+                                                                               <div class="content">
+                                                                                       <p>Qualification and performance testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style4">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_storperf_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="danube/storperf/status-apex.html">
+                                                                               <h2>Storperf</h2>
+                                                                               <div class="content">
+                                                                                       <p>Storage testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style5">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_vsperf_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="danube/vsperf/status-apex.html">
+                                                                               <h2>Vsperf</h2>
+                                                                               <div class="content">
+                                                                                       <p>Virtual switch testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                       </section>
+                                               </div>
+                                       </div>
+
+                               <!-- Footer -->
+                                       <footer id="footer">
+                                               <div class="inner">
+                                                       <section>
+                                                               <h2>OPNFV Testing Working group</h2>
+                                                       </section>
+                                                       <section>
+                                                               <h2>Follow</h2>
+                                                               <ul class="icons">
+                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>
+                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>
+                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>
+                                                               </ul>
+                                                       </section>
+                                                       <ul class="copyright">
+                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>
+                                                       </ul>
+                                               </div>
+                                       </footer>
+
+                       </div>
+
+               <!-- Scripts -->
+                       <script src="3rd_party/js/jquery.min.js"></script>
+                       <script src="3rd_party/js/skel.min.js"></script>
+                       <script src="3rd_party/js/util.js"></script>
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->
+                       <script src="3rd_party/js/main.js"></script>
+
+       </body>
+</html>
similarity index 76%
rename from utils/test/reporting/elements.html
rename to utils/test/reporting/html/elements.html
index 3bfbfc1..7b9bb4d 100644 (file)
@@ -9,10 +9,10 @@
                <title>Elements - Phantom by HTML5 UP</title>\r
                <meta charset="utf-8" />\r
                <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->\r
+               <link rel="stylesheet" href="3rd_party/css/main.css" />\r
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->\r
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->\r
        </head>\r
        <body>\r
                <!-- Wrapper -->\r
@@ -24,7 +24,7 @@
 \r
                                                        <!-- Logo -->\r
                                                                <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
                                                                </a>\r
 \r
                                                        <!-- Nav -->\r
@@ -347,21 +347,21 @@ print 'It took ' + i + ' iterations to sort the deck.';</code></pre>
                                                                        <h3>Fit</h3>\r
                                                                        <div class="box alt">\r
                                                                                <div class="row uniform">\r
-                                                                                       <div class="12u$"><span class="image fit"><img src="images/pic13.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u"><span class="image fit"><img src="images/pic01.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u"><span class="image fit"><img src="images/pic02.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u$"><span class="image fit"><img src="images/pic03.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u"><span class="image fit"><img src="images/pic03.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u"><span class="image fit"><img src="images/pic01.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u$"><span class="image fit"><img src="images/pic02.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u"><span class="image fit"><img src="images/pic02.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u"><span class="image fit"><img src="images/pic03.jpg" alt="" /></span></div>\r
-                                                                                       <div class="4u$"><span class="image fit"><img src="images/pic01.jpg" alt="" /></span></div>\r
+                                                                                       <div class="12u$"><span class="image fit"><img src="img/pic13.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u"><span class="image fit"><img src="img/pic01.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u"><span class="image fit"><img src="img/pic02.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u$"><span class="image fit"><img src="img/pic03.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u"><span class="image fit"><img src="img/pic03.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u"><span class="image fit"><img src="img/pic01.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u$"><span class="image fit"><img src="img/pic02.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u"><span class="image fit"><img src="img/pic02.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u"><span class="image fit"><img src="img/pic03.jpg" alt="" /></span></div>\r
+                                                                                       <div class="4u$"><span class="image fit"><img src="img/pic01.jpg" alt="" /></span></div>\r
                                                                                </div>\r
                                                                        </div>\r
                                                                        <h3>Left &amp; Right</h3>\r
-                                                                       <p><span class="image left"><img src="images/pic14.jpg" alt="" /></span>Fringilla nisl. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent.</p>\r
-                                                                       <p><span class="image right"><img src="images/pic15.jpg" alt="" /></span>Fringilla nisl. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent.</p>\r
+                                                                       <p><span class="image left"><img src="img/pic14.jpg" alt="" /></span>Fringilla nisl. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent.</p>\r
+                                                                       <p><span class="image right"><img src="img/pic15.jpg" alt="" /></span>Fringilla nisl. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent tincidunt felis sagittis eget. tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan eu faucibus. Integer ac pellentesque praesent.</p>\r
                                                                </section>\r
 \r
                                                </div>\r
@@ -409,11 +409,11 @@ print 'It took ' + i + ' iterations to sort the deck.';</code></pre>
                        </div>\r
 \r
                <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
+                       <script src="3rd_party/js/jquery.min.js"></script>\r
+                       <script src="3rd_party/js/skel.min.js"></script>\r
+                       <script src="3rd_party/js/util.js"></script>\r
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->\r
+                       <script src="3rd_party/js/main.js"></script>\r
 \r
        </body>\r
-</html>
\ No newline at end of file
+</html>\r
@@ -9,10 +9,10 @@
                <title>Phantom by HTML5 UP</title>\r
                <meta charset="utf-8" />\r
                <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->\r
+               <link rel="stylesheet" href="3rd_party/css/main.css" />\r
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->\r
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->\r
        </head>\r
        <body>\r
                <!-- Wrapper -->\r
@@ -24,7 +24,7 @@
 \r
                                                        <!-- Logo -->\r
                                                                <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
                                                                </a>\r
 \r
                                                        <!-- Nav -->\r
@@ -56,9 +56,9 @@
                                                        <section class="tiles">\r
                                                                <article class="style5">\r
                                                                        <span class="image">\r
-                                                                               <img src="images/pic05.jpg" alt="" />\r
+                                                                               <img src="img/pic05.jpg" alt="" />\r
                                                                        </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/functest/release/colorado/index-status-apex.html">\r
+                                                                       <a href="colorado/functest/status-apex.html">\r
                                                                                <h2>Status</h2>\r
                                                                                <div class="content">\r
                                                                                        <p>Scenario status</p>\r
@@ -67,9 +67,9 @@
                                                                </article>\r
                                                                <article class="style2">\r
                                                                        <span class="image">\r
-                                                                               <img src="images/pic02.jpg" alt="" />\r
+                                                                               <img src="img/pic02.jpg" alt="" />\r
                                                                        </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/functest/release/colorado/index-vims-apex.html">\r
+                                                                       <a href="colorado/functest/vims-apex.html">\r
                                                                                <h2>vIMS</h2>\r
                                                                                <div class="content">\r
                                                                                        <p>Virtual IMS</p>\r
@@ -78,9 +78,9 @@
                                                                </article>\r
                                                                <article class="style3">\r
                                                                        <span class="image">\r
-                                                                               <img src="images/pic03.jpg" alt="" />\r
+                                                                               <img src="img/pic03.jpg" alt="" />\r
                                                                        </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/functest/release/colorado/index-tempest-apex.html">\r
+                                                                       <a href="colorado/functest/tempest-apex.html">\r
                                                                                <h2>Tempest</h2>\r
                                                                                <div class="content">\r
                                                                                        <p>Tempest OpenStack suite</p>\r
                        </div>\r
 \r
                <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
+                       <script src="3rd_party/js/jquery.min.js"></script>\r
+                       <script src="3rd_party/js/skel.min.js"></script>\r
+                       <script src="3rd_party/js/util.js"></script>\r
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->\r
+                       <script src="3rd_party/js/main.js"></script>\r
 \r
        </body>\r
 </html>\r
similarity index 63%
rename from utils/test/reporting/danube.html
rename to utils/test/reporting/html/functest-danube.html
index 70cd857..ac99cb0 100644 (file)
-<!DOCTYPE HTML>\r
-<!--\r
-       Phantom by HTML5 UP\r
-       html5up.net | @ajlkn\r
-       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)\r
--->\r
-<html>\r
-       <head>\r
-               <title>Phantom by HTML5 UP</title>\r
-               <meta charset="utf-8" />\r
-               <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
-       </head>\r
-       <body>\r
-               <!-- Wrapper -->\r
-                       <div id="wrapper">\r
-\r
-                               <!-- Header -->\r
-                                       <header id="header">\r
-                                               <div class="inner">\r
-\r
-                                                       <!-- Logo -->\r
-                                                               <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
-                                                               </a>\r
-\r
-                                                       <!-- Nav -->\r
-                                                       <!--    <nav>\r
-                                                                       <ul>\r
-                                                                               <li><a href="#menu">Menu</a></li>\r
-                                                                       </ul>\r
-                                                               </nav>\r
-                             --->\r
-                                               </div>\r
-                                       </header>\r
-\r
-                               <!-- Menu -->\r
-                               <!---   <nav id="menu">\r
-                                               <h2>Menu</h2>\r
-                                               <ul>\r
-                                                       <li><a href="index.html">Home</a></li>\r
-                                                       <li><a href="colorado.html">Colorado</a></li>\r
-                                                       <li><a href="danube.html">Danube</a></li>\r
-                                               </ul>\r
-                                       </nav>\r
-                --->\r
-                               <!-- Main -->\r
-                                       <div id="main">\r
-                                               <div class="inner">\r
-                                                       <header>\r
-                                                               <h1>Danube reporting (Master)</h1>\r
-                                                       </header>\r
-                                                       <section class="tiles">\r
-                                                               <article class="style3">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/functest.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="functest-master.html">\r
-                                                                               <h2>Functest</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Functional testing</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                               <article class="style2">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/yardstick.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/yardstick/release/master/index-status-apex.html">\r
-                                                                               <h2>Yardstick</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Qualification and performance testing</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                       </section>\r
-                                               </div>\r
-                                       </div>\r
-\r
-                               <!-- Footer -->\r
-                                       <footer id="footer">\r
-                                               <div class="inner">\r
-                                                       <section>\r
-                                                               <h2>OPNFV Testing Working group</h2>\r
-                                                       </section>\r
-                                                       <section>\r
-                                                               <h2>Follow</h2>\r
-                                                               <ul class="icons">\r
-                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>\r
-                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>\r
-                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>\r
-                                                               </ul>\r
-                                                       </section>\r
-                                                       <ul class="copyright">\r
-                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>\r
-                                                       </ul>\r
-                                               </div>\r
-                                       </footer>\r
-\r
-                       </div>\r
-\r
-               <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
-\r
-       </body>\r
-</html>\r
+<!DOCTYPE HTML>
+<!--
+       Phantom by HTML5 UP
+       html5up.net | @ajlkn
+       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
+-->
+<html>
+       <head>
+               <title>Phantom by HTML5 UP</title>
+               <meta charset="utf-8" />
+               <meta name="viewport" content="width=device-width, initial-scale=1" />
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->
+               <link rel="stylesheet" href="3rd_party/css/main.css" />
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->
+       </head>
+       <body>
+               <!-- Wrapper -->
+                       <div id="wrapper">
+
+                               <!-- Header -->
+                                       <header id="header">
+                                               <div class="inner">
+
+                                                       <!-- Logo -->
+                                                               <a href="index.html" class="logo">
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>
+                                                               </a>
+
+                                                       <!-- Nav -->
+                                                       <!--    <nav>
+                                                                       <ul>
+                                                                               <li><a href="#menu">Menu</a></li>
+                                                                       </ul>
+                                                               </nav>
+                             --->
+                                               </div>
+                                       </header>
+
+                               <!-- Menu -->
+                               <!---   <nav id="menu">
+                                               <h2>Menu</h2>
+                                               <ul>
+                                                       <li><a href="index.html">Home</a></li>
+                                                       <li><a href="colorado.html">Colorado</a></li>
+                                                       <li><a href="danube.html">Danube</a></li>
+                                               </ul>
+                                       </nav>
+                --->
+                               <!-- Main -->
+                                       <div id="main">
+                                               <div class="inner">
+                                                       <header>
+                                                               <h1>Functest reporting</h1>
+                                                       </header>
+                                                       <section class="tiles">
+                                                               <article class="style5">
+                                                                       <span class="image">
+                                                                               <img src="img/pic05.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="danube/functest/status-apex.html">
+                                                                               <h2>Status</h2>
+                                                                               <div class="content">
+                                                                                       <p>Scenario status</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style2">
+                                                                       <span class="image">
+                                                                               <img src="img/pic02.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="danube/functest/vims-apex.html">
+                                                                               <h2>vIMS</h2>
+                                                                               <div class="content">
+                                                                                       <p>Virtual IMS</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style3">
+                                                                       <span class="image">
+                                                                               <img src="img/pic03.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="danube/functest/tempest-apex.html">
+                                                                               <h2>Tempest</h2>
+                                                                               <div class="content">
+                                                                                       <p>Tempest OpenStack suite</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                       </section>
+                                               </div>
+                                       </div>
+
+                               <!-- Footer -->
+                                       <footer id="footer">
+                                               <div class="inner">
+                                                       <section>
+                                                               <h2>OPNFV Testing Working group</h2>
+                                                       </section>
+                                                       <section>
+                                                               <h2>Follow</h2>
+                                                               <ul class="icons">
+                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>
+                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>
+                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>
+                                                               </ul>
+                                                       </section>
+                                                       <ul class="copyright">
+                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>
+                                                       </ul>
+                                               </div>
+                                       </footer>
+
+                       </div>
+
+               <!-- Scripts -->
+                       <script src="3rd_party/js/jquery.min.js"></script>
+                       <script src="3rd_party/js/skel.min.js"></script>
+                       <script src="3rd_party/js/util.js"></script>
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->
+                       <script src="3rd_party/js/main.js"></script>
+
+       </body>
+</html>
similarity index 68%
rename from utils/test/reporting/functest-master.html
rename to utils/test/reporting/html/functest-master.html
index 125b8c3..4b1f763 100644 (file)
-<!DOCTYPE HTML>\r
-<!--\r
-       Phantom by HTML5 UP\r
-       html5up.net | @ajlkn\r
-       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)\r
--->\r
-<html>\r
-       <head>\r
-               <title>Phantom by HTML5 UP</title>\r
-               <meta charset="utf-8" />\r
-               <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
-       </head>\r
-       <body>\r
-               <!-- Wrapper -->\r
-                       <div id="wrapper">\r
-\r
-                               <!-- Header -->\r
-                                       <header id="header">\r
-                                               <div class="inner">\r
-\r
-                                                       <!-- Logo -->\r
-                                                               <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
-                                                               </a>\r
-\r
-                                                       <!-- Nav -->\r
-                                                       <!--    <nav>\r
-                                                                       <ul>\r
-                                                                               <li><a href="#menu">Menu</a></li>\r
-                                                                       </ul>\r
-                                                               </nav>\r
-                             --->\r
-                                               </div>\r
-                                       </header>\r
-\r
-                               <!-- Menu -->\r
-                               <!---   <nav id="menu">\r
-                                               <h2>Menu</h2>\r
-                                               <ul>\r
-                                                       <li><a href="index.html">Home</a></li>\r
-                                                       <li><a href="colorado.html">Colorado</a></li>\r
-                                                       <li><a href="danube.html">Danube</a></li>\r
-                                               </ul>\r
-                                       </nav>\r
-                --->\r
-                               <!-- Main -->\r
-                                       <div id="main">\r
-                                               <div class="inner">\r
-                                                       <header>\r
-                                                               <h1>Functest reporting</h1>\r
-                                                       </header>\r
-                                                       <section class="tiles">\r
-                                                               <article class="style5">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/pic05.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/functest/release/master/index-status-apex.html">\r
-                                                                               <h2>Status</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Scenario status</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                               <article class="style2">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/pic02.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/functest/release/master/index-vims-apex.html">\r
-                                                                               <h2>vIMS</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Virtual IMS</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                               <article class="style3">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/pic03.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="http://testresults.opnfv.org/reporting/functest/release/master/index-tempest-apex.html">\r
-                                                                               <h2>Tempest</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Tempest OpenStack suite</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                       </section>\r
-                                               </div>\r
-                                       </div>\r
-\r
-                               <!-- Footer -->\r
-                                       <footer id="footer">\r
-                                               <div class="inner">\r
-                                                       <section>\r
-                                                               <h2>OPNFV Testing Working group</h2>\r
-                                                       </section>\r
-                                                       <section>\r
-                                                               <h2>Follow</h2>\r
-                                                               <ul class="icons">\r
-                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>\r
-                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>\r
-                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>\r
-                                                               </ul>\r
-                                                       </section>\r
-                                                       <ul class="copyright">\r
-                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>\r
-                                                       </ul>\r
-                                               </div>\r
-                                       </footer>\r
-\r
-                       </div>\r
-\r
-               <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
-\r
-       </body>\r
-</html>\r
+<!DOCTYPE HTML>
+<!--
+       Phantom by HTML5 UP
+       html5up.net | @ajlkn
+       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
+-->
+<html>
+       <head>
+               <title>Phantom by HTML5 UP</title>
+               <meta charset="utf-8" />
+               <meta name="viewport" content="width=device-width, initial-scale=1" />
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->
+               <link rel="stylesheet" href="3rd_party/css/main.css" />
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->
+       </head>
+       <body>
+               <!-- Wrapper -->
+                       <div id="wrapper">
+
+                               <!-- Header -->
+                                       <header id="header">
+                                               <div class="inner">
+
+                                                       <!-- Logo -->
+                                                               <a href="index.html" class="logo">
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>
+                                                               </a>
+
+                                                       <!-- Nav -->
+                                                       <!--    <nav>
+                                                                       <ul>
+                                                                               <li><a href="#menu">Menu</a></li>
+                                                                       </ul>
+                                                               </nav>
+                             --->
+                                               </div>
+                                       </header>
+
+                               <!-- Menu -->
+                               <!---   <nav id="menu">
+                                               <h2>Menu</h2>
+                                               <ul>
+                                                       <li><a href="index.html">Home</a></li>
+                                                       <li><a href="colorado.html">Colorado</a></li>
+                                                       <li><a href="danube.html">Danube</a></li>
+                                               </ul>
+                                       </nav>
+                --->
+                               <!-- Main -->
+                                       <div id="main">
+                                               <div class="inner">
+                                                       <header>
+                                                               <h1>Functest reporting</h1>
+                                                       </header>
+                                                       <section class="tiles">
+                                                               <article class="style5">
+                                                                       <span class="image">
+                                                                               <img src="img/pic05.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="master/functest/status-apex.html">
+                                                                               <h2>Status</h2>
+                                                                               <div class="content">
+                                                                                       <p>Scenario status</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style2">
+                                                                       <span class="image">
+                                                                               <img src="img/pic02.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="master/functest/vims-apex.html">
+                                                                               <h2>vIMS</h2>
+                                                                               <div class="content">
+                                                                                       <p>Virtual IMS</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style3">
+                                                                       <span class="image">
+                                                                               <img src="img/pic03.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="master/functest/tempest-apex.html">
+                                                                               <h2>Tempest</h2>
+                                                                               <div class="content">
+                                                                                       <p>Tempest OpenStack suite</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                       </section>
+                                               </div>
+                                       </div>
+
+                               <!-- Footer -->
+                                       <footer id="footer">
+                                               <div class="inner">
+                                                       <section>
+                                                               <h2>OPNFV Testing Working group</h2>
+                                                       </section>
+                                                       <section>
+                                                               <h2>Follow</h2>
+                                                               <ul class="icons">
+                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>
+                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>
+                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>
+                                                               </ul>
+                                                       </section>
+                                                       <ul class="copyright">
+                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>
+                                                       </ul>
+                                               </div>
+                                       </footer>
+
+                       </div>
+
+               <!-- Scripts -->
+                       <script src="3rd_party/js/jquery.min.js"></script>
+                       <script src="3rd_party/js/skel.min.js"></script>
+                       <script src="3rd_party/js/util.js"></script>
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->
+                       <script src="3rd_party/js/main.js"></script>
+
+       </body>
+</html>
similarity index 83%
rename from utils/test/reporting/generic.html
rename to utils/test/reporting/html/generic.html
index 1351b2a..39273da 100644 (file)
@@ -9,10 +9,10 @@
                <title>Generic - Phantom by HTML5 UP</title>\r
                <meta charset="utf-8" />\r
                <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->\r
+               <link rel="stylesheet" href="3rd_party/css/main.css" />\r
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->\r
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->\r
        </head>\r
        <body>\r
                <!-- Wrapper -->\r
@@ -24,7 +24,7 @@
 \r
                                                        <!-- Logo -->\r
                                                                <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
                                                                </a>\r
 \r
                                                        <!-- Nav -->\r
@@ -53,7 +53,7 @@
                                        <div id="main">\r
                                                <div class="inner">\r
                                                        <h1>Generic Page</h1>\r
-                                                       <span class="image main"><img src="images/pic13.jpg" alt="" /></span>\r
+                                                       <span class="image main"><img src="img/pic13.jpg" alt="" /></span>\r
                                                        <p>Donec eget ex magna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque venenatis dolor imperdiet dolor mattis sagittis. Praesent rutrum sem diam, vitae egestas enim auctor sit amet. Pellentesque leo mauris, consectetur id ipsum sit amet, fergiat. Pellentesque in mi eu massa lacinia malesuada et a elit. Donec urna ex, lacinia in purus ac, pretium pulvinar mauris. Curabitur sapien risus, commodo eget turpis at, elementum convallis elit. Pellentesque enim turpis, hendrerit tristique.</p>\r
                                                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dapibus rutrum facilisis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam tristique libero eu nibh porttitor fermentum. Nullam venenatis erat id vehicula viverra. Nunc ultrices eros ut ultricies condimentum. Mauris risus lacus, blandit sit amet venenatis non, bibendum vitae dolor. Nunc lorem mauris, fringilla in aliquam at, euismod in lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In non lorem sit amet elit placerat maximus. Pellentesque aliquam maximus risus, vel venenatis mauris vehicula hendrerit.</p>\r
                                                        <p>Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque venenatis dolor imperdiet dolor mattis sagittis. Praesent rutrum sem diam, vitae egestas enim auctor sit amet. Pellentesque leo mauris, consectetur id ipsum sit amet, fersapien risus, commodo eget turpis at, elementum convallis elit. Pellentesque enim turpis, hendrerit tristique lorem ipsum dolor.</p>\r
                        </div>\r
 \r
                <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
+                       <script src="3rd_party/js/jquery.min.js"></script>\r
+                       <script src="3rd_party/js/skel.min.js"></script>\r
+                       <script src="3rd_party/js/util.js"></script>\r
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->\r
+                       <script src="3rd_party/js/main.js"></script>\r
 \r
        </body>\r
-</html>
\ No newline at end of file
+</html>\r
similarity index 69%
rename from utils/test/reporting/index.html
rename to utils/test/reporting/html/index.html
index 1fad2e3..c6627ff 100644 (file)
-<!DOCTYPE HTML>\r
-<!--\r
-       Phantom by HTML5 UP\r
-       html5up.net | @ajlkn\r
-       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)\r
--->\r
-<html>\r
-       <head>\r
-               <title>OPNFV reporting</title>\r
-               <meta charset="utf-8" />\r
-               <meta name="viewport" content="width=device-width, initial-scale=1" />\r
-               <!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]-->\r
-               <link rel="stylesheet" href="assets/css/main.css" />\r
-               <!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->\r
-               <!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->\r
-       </head>\r
-       <body>\r
-               <!-- Wrapper -->\r
-                       <div id="wrapper">\r
-\r
-                               <!-- Header -->\r
-                                       <header id="header">\r
-                                               <div class="inner">\r
-\r
-                                                       <!-- Logo -->\r
-                                                               <a href="index.html" class="logo">\r
-                                                                       <span class="symbol"><img src="images/logo.svg" alt="" /></span><span class="title">Phantom</span>\r
-                                                               </a>\r
-\r
-                                                       <!-- Nav -->\r
-                                                       <!--    <nav>\r
-                                                                       <ul>\r
-                                                                               <li><a href="#menu">Menu</a></li>\r
-                                                                       </ul>\r
-                                                               </nav>\r
-                             --->\r
-                                               </div>\r
-                                       </header>\r
-\r
-                               <!-- Menu -->\r
-                               <!---   <nav id="menu">\r
-                                               <h2>Menu</h2>\r
-                                               <ul>\r
-                                                       <li><a href="index.html">Home</a></li>\r
-                                                       <li><a href="colorado.html">Colorado</a></li>\r
-                                                       <li><a href="danube.html">Danube</a></li>\r
-                                               </ul>\r
-                                       </nav>\r
-                --->\r
-                               <!-- Main -->\r
-                                       <div id="main">\r
-                                               <div class="inner">\r
-                                                       <header>\r
-                                                               <h1>OPNFV Testing group reporting</h1>\r
-                                                       </header>\r
-                                                       <section class="tiles">\r
-                                                               <article class="style3">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/colorado.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="colorado.html">\r
-                                                                               <h2>Colorado</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Colorado 1.0 released on the 22nd of September</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                               <article class="style2">\r
-                                                                       <span class="image">\r
-                                                                               <img src="images/danube.jpg" alt="" />\r
-                                                                       </span>\r
-                                                                       <a href="danube.html">\r
-                                                                               <h2>Danube</h2>\r
-                                                                               <div class="content">\r
-                                                                                       <p>Master</p>\r
-                                                                               </div>\r
-                                                                       </a>\r
-                                                               </article>\r
-                                                       </section>\r
-                                               </div>\r
-                                       </div>\r
-\r
-                               <!-- Footer -->\r
-                                       <footer id="footer">\r
-                                               <div class="inner">\r
-                                                       <section>\r
-                                                               <h2>OPNFV Testing Working group</h2>\r
-                                                       </section>\r
-                                                       <section>\r
-                                                               <h2>Follow</h2>\r
-                                                               <ul class="icons">\r
-                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>\r
-                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>\r
-                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>\r
-                                                               </ul>\r
-                                                       </section>\r
-                                                       <ul class="copyright">\r
-                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>\r
-                                                       </ul>\r
-                                               </div>\r
-                                       </footer>\r
-\r
-                       </div>\r
-\r
-               <!-- Scripts -->\r
-                       <script src="assets/js/jquery.min.js"></script>\r
-                       <script src="assets/js/skel.min.js"></script>\r
-                       <script src="assets/js/util.js"></script>\r
-                       <!--[if lte IE 8]><script src="assets/js/ie/respond.min.js"></script><![endif]-->\r
-                       <script src="assets/js/main.js"></script>\r
-\r
-       </body>\r
-</html>\r
+<!DOCTYPE HTML>
+<!--
+       Phantom by HTML5 UP
+       html5up.net | @ajlkn
+       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
+-->
+<html>
+       <head>
+               <title>OPNFV reporting</title>
+               <meta charset="utf-8" />
+               <meta name="viewport" content="width=device-width, initial-scale=1" />
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->
+               <link rel="stylesheet" href="3rd_party/css/main.css" />
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->
+       </head>
+       <body>
+               <!-- Wrapper -->
+                       <div id="wrapper">
+
+                               <!-- Header -->
+                                       <header id="header">
+                                               <div class="inner">
+
+                                                       <!-- Logo -->
+                                                               <a href="index.html" class="logo">
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>
+                                                               </a>
+
+                                                       <!-- Nav -->
+                                                       <!--    <nav>
+                                                                       <ul>
+                                                                               <li><a href="#menu">Menu</a></li>
+                                                                       </ul>
+                                                               </nav>
+                             --->
+                                               </div>
+                                       </header>
+
+                               <!-- Menu -->
+                               <!---   <nav id="menu">
+                                               <h2>Menu</h2>
+                                               <ul>
+                                                       <li><a href="index.html">Home</a></li>
+                                                       <li><a href="colorado.html">Colorado</a></li>
+                                                       <li><a href="danube.html">Danube</a></li>
+                                               </ul>
+                                       </nav>
+                --->
+                               <!-- Main -->
+                                       <div id="main">
+                                               <div class="inner">
+                                                       <header>
+                                                               <h1>OPNFV Testing group reporting</h1>
+                                                       </header>
+                                                       <section class="tiles">
+                                                               <article class="style3">
+                                                                       <span class="image">
+                                                                               <img src="img/colorado.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="colorado.html">
+                                                                               <h2>Colorado</h2>
+                                                                               <div class="content">
+                                                                                       <p>Colorado 1.0 released on the 22nd of September</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style2">
+                                                                       <span class="image">
+                                                                               <img src="img/danube.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="danube.html">
+                                                                               <h2>Danube</h2>
+                                                                               <div class="content">
+                                                                                       <p>Danube 1.0 planned on the 22nd of March</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style6">
+                                                                       <span class="image">
+                                                                               <img src="img/euphrates.jpg" alt="" />
+                                                                       </span>
+                                                                       <a href="master.html">
+                                                                               <h2>Euphrates</h2>
+                                                                               <div class="content">
+                                                                                       <p>Master</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                       </section>
+                                               </div>
+                                       </div>
+
+                               <!-- Footer -->
+                                       <footer id="footer">
+                                               <div class="inner">
+                                                       <section>
+                                                               <h2>OPNFV Testing Working group</h2>
+                                                       </section>
+                                                       <section>
+                                                               <h2>Follow</h2>
+                                                               <ul class="icons">
+                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>
+                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>
+                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>
+                                                               </ul>
+                                                       </section>
+                                                       <ul class="copyright">
+                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>
+                                                       </ul>
+                                               </div>
+                                       </footer>
+
+                       </div>
+
+               <!-- Scripts -->
+                       <script src="3rd_party/js/jquery.min.js"></script>
+                       <script src="3rd_party/js/skel.min.js"></script>
+                       <script src="3rd_party/js/util.js"></script>
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->
+                       <script src="3rd_party/js/main.js"></script>
+
+       </body>
+</html>
diff --git a/utils/test/reporting/html/master.html b/utils/test/reporting/html/master.html
new file mode 100644 (file)
index 0000000..7805386
--- /dev/null
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML>
+<!--
+       Phantom by HTML5 UP
+       html5up.net | @ajlkn
+       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
+-->
+<html>
+       <head>
+               <title>Phantom by HTML5 UP</title>
+               <meta charset="utf-8" />
+               <meta name="viewport" content="width=device-width, initial-scale=1" />
+               <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]-->
+               <link rel="stylesheet" href="3rd_party/css/main.css" />
+               <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]-->
+               <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]-->
+       </head>
+       <body>
+               <!-- Wrapper -->
+                       <div id="wrapper">
+
+                               <!-- Header -->
+                                       <header id="header">
+                                               <div class="inner">
+
+                                                       <!-- Logo -->
+                                                               <a href="index.html" class="logo">
+                                                                       <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span>
+                                                               </a>
+
+                                                       <!-- Nav -->
+                                                       <!--    <nav>
+                                                                       <ul>
+                                                                               <li><a href="#menu">Menu</a></li>
+                                                                       </ul>
+                                                               </nav>
+                             --->
+                                               </div>
+                                       </header>
+
+                               <!-- Menu -->
+                               <!---   <nav id="menu">
+                                               <h2>Menu</h2>
+                                               <ul>
+                                                       <li><a href="index.html">Home</a></li>
+                                                       <li><a href="colorado.html">Colorado</a></li>
+                                                       <li><a href="danube.html">Danube</a></li>
+                                               </ul>
+                                       </nav>
+                --->
+                               <!-- Main -->
+                                       <div id="main">
+                                               <div class="inner">
+                                                       <header>
+                                                               <h1>Master reporting</h1>
+                                                       </header>
+                                                       <section class="tiles">
+                                                               <article class="style3">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_functest_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="functest-master.html">
+                                                                               <h2>Functest</h2>
+                                                                               <div class="content">
+                                                                                       <p>Functional testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style2">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_yardstick_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="master/yardstick/status-apex.html">
+                                                                               <h2>Yardstick</h2>
+                                                                               <div class="content">
+                                                                                       <p>Qualification and performance testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style4">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_storperf_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="master/storperf/status-apex.html">
+                                                                               <h2>Storperf</h2>
+                                                                               <div class="content">
+                                                                                       <p>Storage testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style5">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_vsperf_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="master/vsperf/status-apex.html">
+                                                                               <h2>Vsperf</h2>
+                                                                               <div class="content">
+                                                                                       <p>Virtual switch testing</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style1">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_qtip_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="master/qtip/status-apex.html">
+                                                                               <h2>Qtip</h2>
+                                                                               <div class="content">
+                                                                                       <p>Benchmark as a service</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+                                                               <article class="style6">
+                                                                       <span class="image">
+                                                                               <img src="img/projectIcon_bottlenecks_250x250.png" alt="" />
+                                                                       </span>
+                                                                       <a href="master/bottlenecks/status-apex.html">
+                                                                               <h2>Bottlenecks</h2>
+                                                                               <div class="content">
+                                                                                       <p>Bottleneck finder</p>
+                                                                               </div>
+                                                                       </a>
+                                                               </article>
+
+                                                       </section>
+                                               </div>
+                                       </div>
+
+                               <!-- Footer -->
+                                       <footer id="footer">
+                                               <div class="inner">
+                                                       <section>
+                                                               <h2>OPNFV Testing Working group</h2>
+                                                       </section>
+                                                       <section>
+                                                               <h2>Follow</h2>
+                                                               <ul class="icons">
+                                                                       <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li>
+                                                                       <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li>
+                                                                       <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li>
+                                                               </ul>
+                                                       </section>
+                                                       <ul class="copyright">
+                                                               <li>&copy; Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>
+                                                       </ul>
+                                               </div>
+                                       </footer>
+
+                       </div>
+
+               <!-- Scripts -->
+                       <script src="3rd_party/js/jquery.min.js"></script>
+                       <script src="3rd_party/js/skel.min.js"></script>
+                       <script src="3rd_party/js/util.js"></script>
+                       <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]-->
+                       <script src="3rd_party/js/main.js"></script>
+
+       </body>
+</html>
diff --git a/utils/test/reporting/images/danube.jpg b/utils/test/reporting/images/danube.jpg
deleted file mode 100644 (file)
index a577835..0000000
Binary files a/utils/test/reporting/images/danube.jpg and /dev/null differ
diff --git a/utils/test/reporting/images/functest.jpg b/utils/test/reporting/images/functest.jpg
deleted file mode 100644 (file)
index 3a10da3..0000000
Binary files a/utils/test/reporting/images/functest.jpg and /dev/null differ
diff --git a/utils/test/reporting/images/yardstick.jpg b/utils/test/reporting/images/yardstick.jpg
deleted file mode 100644 (file)
index e0907ca..0000000
Binary files a/utils/test/reporting/images/yardstick.jpg and /dev/null differ
diff --git a/utils/test/reporting/img/danube.jpg b/utils/test/reporting/img/danube.jpg
new file mode 100644 (file)
index 0000000..2d8e27b
Binary files /dev/null and b/utils/test/reporting/img/danube.jpg differ
diff --git a/utils/test/reporting/img/euphrates.jpg b/utils/test/reporting/img/euphrates.jpg
new file mode 100644 (file)
index 0000000..3625b50
Binary files /dev/null and b/utils/test/reporting/img/euphrates.jpg differ
diff --git a/utils/test/reporting/img/gauge_0.png b/utils/test/reporting/img/gauge_0.png
new file mode 100644 (file)
index 0000000..ecefc0e
Binary files /dev/null and b/utils/test/reporting/img/gauge_0.png differ
diff --git a/utils/test/reporting/img/gauge_100.png b/utils/test/reporting/img/gauge_100.png
new file mode 100644 (file)
index 0000000..e199e15
Binary files /dev/null and b/utils/test/reporting/img/gauge_100.png differ
diff --git a/utils/test/reporting/img/gauge_16.7.png b/utils/test/reporting/img/gauge_16.7.png
new file mode 100644 (file)
index 0000000..3e3993c
Binary files /dev/null and b/utils/test/reporting/img/gauge_16.7.png differ
diff --git a/utils/test/reporting/img/gauge_25.png b/utils/test/reporting/img/gauge_25.png
new file mode 100644 (file)
index 0000000..4923659
Binary files /dev/null and b/utils/test/reporting/img/gauge_25.png differ
diff --git a/utils/test/reporting/img/gauge_33.3.png b/utils/test/reporting/img/gauge_33.3.png
new file mode 100644 (file)
index 0000000..364574b
Binary files /dev/null and b/utils/test/reporting/img/gauge_33.3.png differ
diff --git a/utils/test/reporting/img/gauge_41.7.png b/utils/test/reporting/img/gauge_41.7.png
new file mode 100644 (file)
index 0000000..8c3e910
Binary files /dev/null and b/utils/test/reporting/img/gauge_41.7.png differ
diff --git a/utils/test/reporting/img/gauge_50.png b/utils/test/reporting/img/gauge_50.png
new file mode 100644 (file)
index 0000000..2874b9f
Binary files /dev/null and b/utils/test/reporting/img/gauge_50.png differ
diff --git a/utils/test/reporting/img/gauge_58.3.png b/utils/test/reporting/img/gauge_58.3.png
new file mode 100644 (file)
index 0000000..beedc8a
Binary files /dev/null and b/utils/test/reporting/img/gauge_58.3.png differ
diff --git a/utils/test/reporting/img/gauge_66.7.png b/utils/test/reporting/img/gauge_66.7.png
new file mode 100644 (file)
index 0000000..93f44d1
Binary files /dev/null and b/utils/test/reporting/img/gauge_66.7.png differ
diff --git a/utils/test/reporting/img/gauge_75.png b/utils/test/reporting/img/gauge_75.png
new file mode 100644 (file)
index 0000000..9fc261f
Binary files /dev/null and b/utils/test/reporting/img/gauge_75.png differ
diff --git a/utils/test/reporting/img/gauge_8.3.png b/utils/test/reporting/img/gauge_8.3.png
new file mode 100644 (file)
index 0000000..59f8657
Binary files /dev/null and b/utils/test/reporting/img/gauge_8.3.png differ
diff --git a/utils/test/reporting/img/gauge_83.3.png b/utils/test/reporting/img/gauge_83.3.png
new file mode 100644 (file)
index 0000000..27ae4ec
Binary files /dev/null and b/utils/test/reporting/img/gauge_83.3.png differ
diff --git a/utils/test/reporting/img/gauge_91.7.png b/utils/test/reporting/img/gauge_91.7.png
new file mode 100644 (file)
index 0000000..2808657
Binary files /dev/null and b/utils/test/reporting/img/gauge_91.7.png differ
diff --git a/utils/test/reporting/img/icon-nok.png b/utils/test/reporting/img/icon-nok.png
new file mode 100644 (file)
index 0000000..526b529
Binary files /dev/null and b/utils/test/reporting/img/icon-nok.png differ
diff --git a/utils/test/reporting/img/icon-ok.png b/utils/test/reporting/img/icon-ok.png
new file mode 100644 (file)
index 0000000..3a9de2e
Binary files /dev/null and b/utils/test/reporting/img/icon-ok.png differ
diff --git a/utils/test/reporting/img/projectIcon_bottlenecks.png b/utils/test/reporting/img/projectIcon_bottlenecks.png
new file mode 100644 (file)
index 0000000..8b78f88
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_bottlenecks.png differ
diff --git a/utils/test/reporting/img/projectIcon_bottlenecks_250x250.png b/utils/test/reporting/img/projectIcon_bottlenecks_250x250.png
new file mode 100644 (file)
index 0000000..ec6f58d
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_bottlenecks_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_bottlenecks_60x60.png b/utils/test/reporting/img/projectIcon_bottlenecks_60x60.png
new file mode 100644 (file)
index 0000000..a09d57d
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_bottlenecks_60x60.png differ
diff --git a/utils/test/reporting/img/projectIcon_dovetail.png b/utils/test/reporting/img/projectIcon_dovetail.png
new file mode 100644 (file)
index 0000000..a97767c
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_dovetail.png differ
diff --git a/utils/test/reporting/img/projectIcon_dovetail_250x250.png b/utils/test/reporting/img/projectIcon_dovetail_250x250.png
new file mode 100644 (file)
index 0000000..d9ff0ef
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_dovetail_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_dovetail_60x60.png b/utils/test/reporting/img/projectIcon_dovetail_60x60.png
new file mode 100644 (file)
index 0000000..2db3184
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_dovetail_60x60.png differ
diff --git a/utils/test/reporting/img/projectIcon_functest.png b/utils/test/reporting/img/projectIcon_functest.png
new file mode 100644 (file)
index 0000000..cc9d17c
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_functest.png differ
diff --git a/utils/test/reporting/img/projectIcon_functest_250x250.png b/utils/test/reporting/img/projectIcon_functest_250x250.png
new file mode 100644 (file)
index 0000000..54f08f8
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_functest_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_functest_60x60.png b/utils/test/reporting/img/projectIcon_functest_60x60.png
new file mode 100644 (file)
index 0000000..dead753
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_functest_60x60.png differ
diff --git a/utils/test/reporting/img/projectIcon_qtip.png b/utils/test/reporting/img/projectIcon_qtip.png
new file mode 100644 (file)
index 0000000..9bcaf47
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_qtip.png differ
diff --git a/utils/test/reporting/img/projectIcon_qtip_250x250.png b/utils/test/reporting/img/projectIcon_qtip_250x250.png
new file mode 100644 (file)
index 0000000..10abd15
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_qtip_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_qtip_60x60.png b/utils/test/reporting/img/projectIcon_qtip_60x60.png
new file mode 100644 (file)
index 0000000..6e756a7
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_qtip_60x60.png differ
diff --git a/utils/test/reporting/img/projectIcon_storperf.png b/utils/test/reporting/img/projectIcon_storperf.png
new file mode 100644 (file)
index 0000000..69b4bfc
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_storperf.png differ
diff --git a/utils/test/reporting/img/projectIcon_storperf_250x250.png b/utils/test/reporting/img/projectIcon_storperf_250x250.png
new file mode 100644 (file)
index 0000000..e333789
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_storperf_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_storperf_60x60.png b/utils/test/reporting/img/projectIcon_storperf_60x60.png
new file mode 100644 (file)
index 0000000..7dd3702
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_storperf_60x60.png differ
diff --git a/utils/test/reporting/img/projectIcon_vsperf.png b/utils/test/reporting/img/projectIcon_vsperf.png
new file mode 100644 (file)
index 0000000..4558009
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_vsperf.png differ
diff --git a/utils/test/reporting/img/projectIcon_vsperf_250x250.png b/utils/test/reporting/img/projectIcon_vsperf_250x250.png
new file mode 100644 (file)
index 0000000..0b70029
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_vsperf_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_vsperf_60x60.png b/utils/test/reporting/img/projectIcon_vsperf_60x60.png
new file mode 100644 (file)
index 0000000..b066a59
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_vsperf_60x60.png differ
diff --git a/utils/test/reporting/img/projectIcon_yardstick.png b/utils/test/reporting/img/projectIcon_yardstick.png
new file mode 100644 (file)
index 0000000..5e868f1
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_yardstick.png differ
diff --git a/utils/test/reporting/img/projectIcon_yardstick_250x250.png b/utils/test/reporting/img/projectIcon_yardstick_250x250.png
new file mode 100644 (file)
index 0000000..8acccc0
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_yardstick_250x250.png differ
diff --git a/utils/test/reporting/img/projectIcon_yardstick_60x60.png b/utils/test/reporting/img/projectIcon_yardstick_60x60.png
new file mode 100644 (file)
index 0000000..ee3a20b
Binary files /dev/null and b/utils/test/reporting/img/projectIcon_yardstick_60x60.png differ
diff --git a/utils/test/reporting/img/weather-clear.png b/utils/test/reporting/img/weather-clear.png
new file mode 100644 (file)
index 0000000..a0d9677
Binary files /dev/null and b/utils/test/reporting/img/weather-clear.png differ
diff --git a/utils/test/reporting/img/weather-few-clouds.png b/utils/test/reporting/img/weather-few-clouds.png
new file mode 100644 (file)
index 0000000..acfa783
Binary files /dev/null and b/utils/test/reporting/img/weather-few-clouds.png differ
diff --git a/utils/test/reporting/img/weather-overcast.png b/utils/test/reporting/img/weather-overcast.png
new file mode 100644 (file)
index 0000000..4296246
Binary files /dev/null and b/utils/test/reporting/img/weather-overcast.png differ
diff --git a/utils/test/reporting/img/weather-storm.png b/utils/test/reporting/img/weather-storm.png
new file mode 100644 (file)
index 0000000..956f0e2
Binary files /dev/null and b/utils/test/reporting/img/weather-storm.png differ
diff --git a/utils/test/reporting/pages/Gruntfile.js b/utils/test/reporting/pages/Gruntfile.js
new file mode 100644 (file)
index 0000000..c1ba20e
--- /dev/null
@@ -0,0 +1,519 @@
+// Generated on 2016-12-19 using generator-angular 0.15.1
+'use strict';
+
+// # Globbing
+// for performance reasons we're only matching one level down:
+// 'test/spec/{,*/}*.js'
+// use this if you want to recursively match all subfolders:
+// 'test/spec/**/*.js'
+
+module.exports = function (grunt) {
+
+  // Time how long tasks take. Can help when optimizing build times
+  require('time-grunt')(grunt);
+
+  // Automatically load required Grunt tasks
+  require('jit-grunt')(grunt, {
+    useminPrepare: 'grunt-usemin',
+    ngtemplates: 'grunt-angular-templates',
+    cdnify: 'grunt-google-cdn'
+  });
+
+  // Configurable paths for the application
+  var appConfig = {
+    app: require('./bower.json').appPath || 'app',
+    dist: 'dist'
+  };
+
+  // Define the configuration for all the tasks
+  grunt.initConfig({
+
+    // Project settings
+    yeoman: appConfig,
+
+    // Watches files for changes and runs tasks based on the changed files
+    watch: {
+      bower: {
+        files: ['bower.json'],
+        tasks: ['wiredep']
+      },
+      js: {
+        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
+        tasks: ['newer:jshint:all', 'newer:jscs:all'],
+        options: {
+          livereload: '<%= connect.options.livereload %>'
+        }
+      },
+      jsTest: {
+        files: ['test/spec/{,*/}*.js'],
+        tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma']
+      },
+      styles: {
+        files: ['<%= yeoman.app %>/styles/{,*/}*.css','<%= yeoman.app %>/styles/font-awesome/css/{,*/}*.css'],
+        tasks: ['newer:copy:styles', 'postcss']
+      },
+      gruntfile: {
+        files: ['Gruntfile.js']
+      },
+      livereload: {
+        options: {
+          livereload: '<%= connect.options.livereload %>'
+        },
+        files: [
+          '<%= yeoman.app %>/{,*/}*.html',
+          '.tmp/styles/{,*/}*.css',
+          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
+          '<%= yeoman.app %>/views/{,*/}*.html'
+
+        ]
+      }
+    },
+
+    // The actual grunt server settings
+    connect: {
+      options: {
+        port: 9000,
+        // Change this to '0.0.0.0' to access the server from outside.
+        hostname: 'localhost',
+        livereload: 35729
+      },
+      livereload: {
+        options: {
+          open: true,
+          middleware: function (connect) {
+            return [
+              connect.static('.tmp'),
+              connect().use(
+                '/bower_components',
+                connect.static('./bower_components')
+              ),
+              connect().use(
+                '/app/styles',
+                connect.static('./app/styles')
+              ),
+              connect.static(appConfig.app)
+            ];
+          }
+        }
+      },
+      test: {
+        options: {
+          port: 9001,
+          middleware: function (connect) {
+            return [
+              connect.static('.tmp'),
+              connect.static('test'),
+              connect().use(
+                '/bower_components',
+                connect.static('./bower_components')
+              ),
+              connect.static(appConfig.app)
+            ];
+          }
+        }
+      },
+      dist: {
+        options: {
+          open: true,
+          base: '<%= yeoman.dist %>'
+        }
+      }
+    },
+
+    // Make sure there are no obvious mistakes
+    jshint: {
+      options: {
+        jshintrc: '.jshintrc',
+        reporter: require('jshint-stylish')
+      },
+      all: {
+        src: [
+          'Gruntfile.js',
+          '<%= yeoman.app %>/scripts/{,*/}*.js'
+        ]
+      },
+      test: {
+        options: {
+          jshintrc: 'test/.jshintrc'
+        },
+        src: ['test/spec/{,*/}*.js']
+      }
+    },
+
+    // Make sure code styles are up to par
+    jscs: {
+      options: {
+        config: '.jscsrc',
+        verbose: true
+      },
+      all: {
+        src: [
+          'Gruntfile.js',
+          '<%= yeoman.app %>/scripts/{,*/}*.js'
+        ]
+      },
+      test: {
+        src: ['test/spec/{,*/}*.js']
+      }
+    },
+
+    // Empties folders to start fresh
+    clean: {
+      dist: {
+        files: [{
+          dot: true,
+          src: [
+            '.tmp',
+            '<%= yeoman.dist %>/{,*/}*',
+            '!<%= yeoman.dist %>/.git{,*/}*'
+          ]
+        }]
+      },
+      server: '.tmp'
+    },
+
+    // Add vendor prefixed styles
+    postcss: {
+      options: {
+        processors: [
+          require('autoprefixer-core')({ browsers: ['last 1 version'] })
+        ]
+      },
+      server: {
+        options: {
+          map: true
+        },
+        files: [{
+          expand: true,
+          cwd: '.tmp/styles/',
+          src: '{,*/}*.css',
+          dest: '.tmp/styles/'
+        }]
+      },
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '.tmp/styles/',
+          src: '{,*/}*.css',
+          dest: '.tmp/styles/'
+        }]
+      }
+    },
+
+    // Automatically inject Bower components into the app
+    wiredep: {
+      app: {
+        src: ['<%= yeoman.app %>/index.html'],
+        ignorePath: /\.\.\//
+      },
+      test: {
+        devDependencies: true,
+        src: '<%= karma.unit.configFile %>',
+        ignorePath: /\.\.\//,
+        fileTypes: {
+          js: {
+            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
+            detect: {
+              js: /'(.*\.js)'/gi
+            },
+            replace: {
+              js: '\'{{filePath}}\','
+            }
+          }
+        }
+      }
+    },
+
+    // Renames files for browser caching purposes
+    filerev: {
+      dist: {
+        src: [
+          '<%= yeoman.dist %>/scripts/{,*/}*.js',
+          '<%= yeoman.dist %>/styles/{,*/}*.css',
+          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
+          '<%= yeoman.dist %>/styles/fonts/*'
+        ]
+      }
+    },
+
+    // Reads HTML for usemin blocks to enable smart builds that automatically
+    // concat, minify and revision files. Creates configurations in memory so
+    // additional tasks can operate on them
+    useminPrepare: {
+      html: '<%= yeoman.app %>/index.html',
+      options: {
+        dest: '<%= yeoman.dist %>',
+        flow: {
+          html: {
+            steps: {
+              js: ['concat', 'uglifyjs'],
+              css: ['cssmin']
+            },
+            post: {}
+          }
+        }
+      }
+    },
+
+    // Performs rewrites based on filerev and the useminPrepare configuration
+    usemin: {
+      html: ['<%= yeoman.dist %>/{,*/}*.html'],
+      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
+      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
+      options: {
+        assetsDirs: [
+          '<%= yeoman.dist %>',
+          '<%= yeoman.dist %>/images',
+          '<%= yeoman.dist %>/styles'
+        ],
+        patterns: {
+          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
+        }
+      }
+    },
+
+    // The following *-min tasks will produce minified files in the dist folder
+    // By default, your `index.html`'s <!-- Usemin block --> will take care of
+    // minification. These next options are pre-configured if you do not wish
+    // to use the Usemin blocks.
+    // cssmin: {
+    //   dist: {
+    //     files: {
+    //       '<%= yeoman.dist %>/styles/main.css': [
+    //         '.tmp/styles/{,*/}*.css'
+    //       ]
+    //     }
+    //   }
+    // },
+    // uglify: {
+    //   dist: {
+    //     files: {
+    //       '<%= yeoman.dist %>/scripts/scripts.js': [
+    //         '<%= yeoman.dist %>/scripts/scripts.js'
+    //       ]
+    //     }
+    //   }
+    // },
+    // concat: {
+    //   dist: {}
+    // },
+
+    imagemin: {
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.app %>/images',
+          src: '{,*/}*.{png,jpg,jpeg,gif}',
+          dest: '<%= yeoman.dist %>/images'
+        }]
+      }
+      // dist:{
+      //   files:[{
+      //     expand:true,
+      //     cwd:'<%= yeoman.app %>/bower_components/',
+      //     src:'**/*.{png,jpg,gif}',
+      //     dest: '<%= yeoman.dist %>/styles'
+      //   }
+
+      //   ]
+      // }
+    },
+
+    // bowerimagemin:{
+    //   dist:{
+    //     files:[{
+    //       expand: true,
+    // cwd: '<%= yeoman.app %>/bower_components',
+    //       src:'{,*/}*.{png,jpg,jpeg,gif}',
+    //       dest: '<%= yeoman.dist %>/styles'
+    //     }
+
+    //     ]
+    //   }
+    // },
+
+
+    svgmin: {
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.app %>/images',
+          src: '{,*/}*.svg',
+          dest: '<%= yeoman.dist %>/images'
+        }]
+      }
+    },
+
+    htmlmin: {
+      dist: {
+        options: {
+          collapseWhitespace: true,
+          conservativeCollapse: true,
+          collapseBooleanAttributes: true,
+          removeCommentsFromCDATA: true
+        },
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.dist %>',
+          src: ['*.html'],
+          dest: '<%= yeoman.dist %>'
+        }]
+      }
+    },
+
+    ngtemplates: {
+      dist: {
+        options: {
+          module: 'opnfvApp',
+          htmlmin: '<%= htmlmin.dist.options %>',
+          usemin: 'scripts/scripts.js'
+        },
+        cwd: '<%= yeoman.app %>',
+        src: 'views/{,*/}*.html',
+        dest: '.tmp/templateCache.js'
+      }
+    },
+
+    // ng-annotate tries to make the code safe for minification automatically
+    // by using the Angular long form for dependency injection.
+    ngAnnotate: {
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '.tmp/concat/scripts',
+          src: '*.js',
+          dest: '.tmp/concat/scripts'
+        }]
+      }
+    },
+
+    // Replace Google CDN references
+    cdnify: {
+      dist: {
+        html: ['<%= yeoman.dist %>/*.html']
+      }
+    },
+
+    // Copies remaining files to places other tasks can use
+    copy: {
+      dist: {
+        files: [{
+          expand: true,
+          dot: true,
+          cwd: '<%= yeoman.app %>',
+          dest: '<%= yeoman.dist %>',
+          src: [
+            '*.{ico,png,txt}',
+            '*.html',
+            'images/{,*/}*.{webp}',
+            'styles/fonts/{,*/}*.*'
+
+          ]
+        }, {
+          expand: true,
+          cwd: '.tmp/images',
+          dest: '<%= yeoman.dist %>/images',
+          src: ['generated/*']
+        }, {
+          expand: true,
+          cwd: 'bower_components/bootstrap/dist',
+          src: 'fonts/*',
+          dest: '<%= yeoman.dist %>'
+        },{
+          expand:true,
+          cwd:'bower_components/components-font-awesome',
+          src:'fonts/*',
+          dest: '<%=yeoman.dist%>'
+        }
+        ]
+      },
+      styles: {
+        expand: true,
+        cwd: '<%= yeoman.app %>/styles',
+        dest: '.tmp/styles/',
+        src: ['{,*/}*.css',
+          'bower_components/{,*/}*.{png,jpg,jpeg,gif}'
+        ]
+      }
+    },
+
+    // Run some tasks in parallel to speed up the build process
+    concurrent: {
+      server: [
+        'copy:styles'
+      ],
+      test: [
+        'copy:styles'
+      ],
+      dist: [
+        'copy:styles',
+
+        'imagemin',
+        'svgmin'
+      ]
+    },
+
+    // Test settings
+    karma: {
+      unit: {
+        configFile: 'test/karma.conf.js',
+        singleRun: true
+      }
+    }
+  });
+
+
+  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
+    if (target === 'dist') {
+      return grunt.task.run(['build', 'connect:dist:keepalive']);
+    }
+
+    grunt.task.run([
+      'clean:server',
+      'wiredep',
+      'concurrent:server',
+      'postcss:server',
+      'connect:livereload',
+      'watch'
+    ]);
+  });
+
+  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
+    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
+    grunt.task.run(['serve:' + target]);
+  });
+
+  grunt.registerTask('test', [
+    'clean:server',
+    'wiredep',
+    'concurrent:test',
+    'postcss',
+    'connect:test',
+    'karma'
+  ]);
+
+  grunt.registerTask('build', [
+    'clean:dist',
+    'wiredep',
+    'useminPrepare',
+    'concurrent:dist',
+    'postcss',
+    'ngtemplates',
+    'concat',
+    'ngAnnotate',
+    'copy:dist',
+    // 'cdnify',
+    'cssmin',
+    'uglify',
+    'filerev',
+    'usemin',
+    'htmlmin'
+  ]);
+
+  grunt.registerTask('default', [
+    'newer:jshint',
+    'newer:jscs',
+    'test',
+    'build'
+  ]);
+};
diff --git a/utils/test/reporting/pages/angular.sh b/utils/test/reporting/pages/angular.sh
new file mode 100755 (executable)
index 0000000..080f27b
--- /dev/null
@@ -0,0 +1,12 @@
+: ${SERVER_URL:='http://testresults.opnfv.org/reporting/api'}
+
+echo "var BASE_URL = 'http://${SERVER_URL}/landing-page'" >> app/scripts/app.config.js
+echo "var PROJECT_URL = 'http://${SERVER_URL}'" >> app/scripts/app.config.js
+
+apt-get install -y nodejs
+apt-get install -y npm
+npm install
+npm install -g grunt
+npm install -g bower
+bower install --force --allow-root
+grunt build
diff --git a/utils/test/reporting/pages/app/404.html b/utils/test/reporting/pages/app/404.html
new file mode 100644 (file)
index 0000000..899828a
--- /dev/null
@@ -0,0 +1,152 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Page Not Found :(</title>
+    <style>
+      ::-moz-selection {
+        background: #b3d4fc;
+        text-shadow: none;
+      }
+
+      ::selection {
+        background: #b3d4fc;
+        text-shadow: none;
+      }
+
+      html {
+        padding: 30px 10px;
+        font-size: 20px;
+        line-height: 1.4;
+        color: #737373;
+        background: #f0f0f0;
+        -webkit-text-size-adjust: 100%;
+        -ms-text-size-adjust: 100%;
+      }
+
+      html,
+      input {
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      }
+
+      body {
+        max-width: 500px;
+        padding: 30px 20px 50px;
+        border: 1px solid #b3b3b3;
+        border-radius: 4px;
+        margin: 0 auto;
+        box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
+        background: #fcfcfc;
+      }
+
+      h1 {
+        margin: 0 10px;
+        font-size: 50px;
+        text-align: center;
+      }
+
+      h1 span {
+        color: #bbb;
+      }
+
+      h3 {
+        margin: 1.5em 0 0.5em;
+      }
+
+      p {
+        margin: 1em 0;
+      }
+
+      ul {
+        padding: 0 0 0 40px;
+        margin: 1em 0;
+      }
+
+      .container {
+        max-width: 380px;
+        margin: 0 auto;
+      }
+
+      /* google search */
+
+      #goog-fixurl ul {
+        list-style: none;
+        padding: 0;
+        margin: 0;
+      }
+
+      #goog-fixurl form {
+        margin: 0;
+      }
+
+      #goog-wm-qt,
+      #goog-wm-sb {
+        border: 1px solid #bbb;
+        font-size: 16px;
+        line-height: normal;
+        vertical-align: top;
+        color: #444;
+        border-radius: 2px;
+      }
+
+      #goog-wm-qt {
+        width: 220px;
+        height: 20px;
+        padding: 5px;
+        margin: 5px 10px 0 0;
+        box-shadow: inset 0 1px 1px #ccc;
+      }
+
+      #goog-wm-sb {
+        display: inline-block;
+        height: 32px;
+        padding: 0 10px;
+        margin: 5px 0 0;
+        white-space: nowrap;
+        cursor: pointer;
+        background-color: #f5f5f5;
+        background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        -webkit-appearance: none;
+        -moz-appearance: none;
+        appearance: none;
+      }
+
+      #goog-wm-sb:hover,
+      #goog-wm-sb:focus {
+        border-color: #aaa;
+        box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+        background-color: #f8f8f8;
+      }
+
+      #goog-wm-qt:hover,
+      #goog-wm-qt:focus {
+        border-color: #105cb6;
+        outline: 0;
+        color: #222;
+      }
+
+      input::-moz-focus-inner {
+        padding: 0;
+        border: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="container">
+      <h1>Not found <span>:(</span></h1>
+      <p>Sorry, but the page you were trying to view does not exist.</p>
+      <p>It looks like this was the result of either:</p>
+      <ul>
+        <li>a mistyped address</li>
+        <li>an out-of-date link</li>
+      </ul>
+      <script>
+        var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
+      </script>
+      <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
+    </div>
+  </body>
+</html>
diff --git a/utils/test/reporting/pages/app/favicon.ico b/utils/test/reporting/pages/app/favicon.ico
new file mode 100644 (file)
index 0000000..6527905
Binary files /dev/null and b/utils/test/reporting/pages/app/favicon.ico differ
diff --git a/utils/test/reporting/pages/app/images/green.png b/utils/test/reporting/pages/app/images/green.png
new file mode 100644 (file)
index 0000000..57fc599
Binary files /dev/null and b/utils/test/reporting/pages/app/images/green.png differ
diff --git a/utils/test/reporting/pages/app/images/green@2x.png b/utils/test/reporting/pages/app/images/green@2x.png
new file mode 100644 (file)
index 0000000..3bda5be
Binary files /dev/null and b/utils/test/reporting/pages/app/images/green@2x.png differ
diff --git a/utils/test/reporting/pages/app/images/header_one.jpg b/utils/test/reporting/pages/app/images/header_one.jpg
new file mode 100644 (file)
index 0000000..69f377c
Binary files /dev/null and b/utils/test/reporting/pages/app/images/header_one.jpg differ
diff --git a/utils/test/reporting/pages/app/images/logo.png b/utils/test/reporting/pages/app/images/logo.png
new file mode 100644 (file)
index 0000000..1519503
Binary files /dev/null and b/utils/test/reporting/pages/app/images/logo.png differ
diff --git a/utils/test/reporting/pages/app/images/overview.png b/utils/test/reporting/pages/app/images/overview.png
new file mode 100644 (file)
index 0000000..14dbbff
Binary files /dev/null and b/utils/test/reporting/pages/app/images/overview.png differ
diff --git a/utils/test/reporting/pages/app/images/word_map.png b/utils/test/reporting/pages/app/images/word_map.png
new file mode 100644 (file)
index 0000000..3b3b9d2
Binary files /dev/null and b/utils/test/reporting/pages/app/images/word_map.png differ
diff --git a/utils/test/reporting/pages/app/images/yeoman.png b/utils/test/reporting/pages/app/images/yeoman.png
new file mode 100644 (file)
index 0000000..92497ad
Binary files /dev/null and b/utils/test/reporting/pages/app/images/yeoman.png differ
diff --git a/utils/test/reporting/pages/app/index.html b/utils/test/reporting/pages/app/index.html
new file mode 100644 (file)
index 0000000..f4eb65a
--- /dev/null
@@ -0,0 +1,96 @@
+<!doctype html>
+<html ng-app="opnfvApp">
+
+<head>
+    <meta charset="utf-8">
+    <title>OPNFV-DASHBOARD EXAMPLE</title>
+    <meta name="description" content="">
+    <meta name="viewport" content="width=device-width,width=device-width,initial-scale=1">
+    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
+    <!-- build:css(.) styles/vendor.css -->
+    <!-- bower:css -->
+    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
+    <link rel="stylesheet" href="bower_components/metisMenu/dist/metisMenu.css" />
+    <link rel="stylesheet" href="bower_components/chosen/chosen.css" />
+    <link rel="stylesheet" href="bower_components/selectize/dist/css/selectize.css" />
+    <link rel="stylesheet" href="bower_components/components-font-awesome/css/font-awesome.css" />
+    <link rel="stylesheet" href="bower_components/angular-tooltips/dist/angular-tooltips.min.css" />
+    <link rel="stylesheet" href="bower_components/animate.css/animate.css" />
+    <link rel="stylesheet" href="bower_components/ng-dialog/css/ngDialog.css" />
+    <link rel="stylesheet" href="bower_components/ng-dialog/css/ngDialog-theme-default.css" />
+    <link rel="stylesheet" href="bower_components/inspiniacss/style.css" />
+    <link rel="stylesheet" href="bower_components/angular-loading/angular-loading.css" />
+    <!-- endbower -->
+    <!-- endbuild -->
+    <!-- build:css(.tmp) styles/style.css -->
+
+    <link rel="stylesheet" href="styles/custome.css">
+
+
+    <!-- endbuild -->
+</head>
+
+<body class="{{$state.current.data.specialClass}}" id="page-top">
+    <!--[if lte IE 8]>
+      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
+    <![endif]-->
+
+    <!-- Add your site or application content here -->
+    <div ui-view></div>
+    <!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
+    <!--<script>
+       !function(A,n,g,u,l,a,r){A.GoogleAnalyticsObject=l,A[l]=A[l]||function(){
+       (A[l].q=A[l].q||[]).push(arguments)},A[l].l=+new Date,a=n.createElement(g),
+       r=n.getElementsByTagName(g)[0],a.src=u,r.parentNode.insertBefore(a,r)
+       }(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+       ga('create', 'UA-XXXXX-X');
+       ga('send', 'pageview');
+    </script>-->
+
+    <!-- build:js(.) scripts/vendor.js -->
+    <!-- bower:js -->
+    <script src="bower_components/jquery/dist/jquery.js"></script>
+    <script src="bower_components/angular/angular.js"></script>
+    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
+    <script src="bower_components/angular-animate/angular-animate.js"></script>
+    <script src="bower_components/jquery-slimscroll/jquery.slimscroll.js"></script>
+    <script src="bower_components/jquery-slimscroll/jquery.slimscroll.min.js"></script>
+    <script src="bower_components/metisMenu/dist/metisMenu.js"></script>
+    <script src="bower_components/chosen/chosen.jquery.js"></script>
+    <script src="bower_components/oclazyload/dist/ocLazyLoad.js"></script>
+    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
+    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
+    <script src="bower_components/angular-resource/angular-resource.js"></script>
+    <script src="bower_components/sifter/sifter.js"></script>
+    <script src="bower_components/microplugin/src/microplugin.js"></script>
+    <script src="bower_components/selectize/dist/js/selectize.js"></script>
+    <script src="bower_components/angular-selectize2/dist/angular-selectize.js"></script>
+    <script src="bower_components/angular-tooltips/dist/angular-tooltips.min.js"></script>
+    <script src="bower_components/jQuery-rwdImageMaps/jquery.rwdImageMaps.min.js"></script>
+    <script src="bower_components/ng-dialog/js/ngDialog.js"></script>
+    <script src="bower_components/angularUtils-pagination/dirPagination.js"></script>
+    <script src="bower_components/spin.js/spin.js"></script>
+    <script src="bower_components/angular-loading/angular-loading.js"></script>
+    <script src="bower_components/spin.js/spin.js"></script>
+    <!-- endbower -->
+    <!-- endbuild -->
+
+    <!-- build:js({.tmp,app}) scripts/scripts.js -->
+    <script src="scripts/app.js"></script>
+    <!--<script src="scripts/controllers/main.js"></script>-->
+    <script src="scripts/config.router.js"></script>
+    <script src="scripts/controllers/table.controller.js"></script>
+    <script src="scripts/config.js"></script>
+    <script src="scripts/factory/table.factory.js"></script>
+    <script src="scripts/controllers/case.controller.js"></script>
+    <script src="scripts/controllers/auth.controller.js"></script>
+    <script src="scripts/controllers/admin.controller.js"></script>
+    <script src="scripts/controllers/main.controller.js"></script>
+    <script src="scripts/app.config.js"></script>
+    <script src="scripts/controllers/testvisual.controller.js"></script>
+
+    <!-- endbuild -->
+</body>
+
+</html>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/robots.txt b/utils/test/reporting/pages/app/robots.txt
new file mode 100644 (file)
index 0000000..4d521f9
--- /dev/null
@@ -0,0 +1,4 @@
+# robotstxt.org
+
+User-agent: *
+Disallow:
diff --git a/utils/test/reporting/pages/app/scripts/app.config.js b/utils/test/reporting/pages/app/scripts/app.config.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/reporting/pages/app/scripts/app.js b/utils/test/reporting/pages/app/scripts/app.js
new file mode 100644 (file)
index 0000000..043e3ca
--- /dev/null
@@ -0,0 +1,24 @@
+'use strict';
+
+/**
+ * @ngdoc overview
+ * @name opnfvApp
+ * @description
+ * # opnfvApp
+ *
+ * Main module of the application.
+ */
+angular
+    .module('opnfvApp', [
+        'ngAnimate',
+        'ui.router',
+        'oc.lazyLoad',
+        'ui.bootstrap',
+        'ngResource',
+        'selectize',
+        '720kb.tooltips',
+        'ngDialog',
+        'angularUtils.directives.dirPagination',
+        'darthwade.dwLoading'
+
+    ]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/config.js b/utils/test/reporting/pages/app/scripts/config.js
new file mode 100644 (file)
index 0000000..1010169
--- /dev/null
@@ -0,0 +1,19 @@
+/**
+ * @ngdoc overview
+ * @name opnfvApp
+ * @description
+ * # opnfvApp
+ *
+ * Main config file of the application.
+ */
+angular
+    .module('opnfvApp').config(['$httpProvider', '$qProvider', function($httpProvider, $qProvider) {
+
+            $httpProvider.defaults.useXDomain = true;
+            delete $httpProvider.defaults.headers.common['X-Requested-With'];
+
+            $qProvider.errorOnUnhandledRejections(false);
+
+        }
+
+    ]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/config.router.js b/utils/test/reporting/pages/app/scripts/config.router.js
new file mode 100644 (file)
index 0000000..d3724b7
--- /dev/null
@@ -0,0 +1,84 @@
+'use strict'
+/**
+ * @ngdoc function
+ * @name opnfvdashBoardAngularApp.config:config.router.js
+ * @description config of the ui router and lazy load setting
+ * config of the opnfvdashBoardAngularApp
+ */
+angular.module('opnfvApp')
+    .run([
+        '$rootScope', '$state', '$stateParams',
+        function($rootScope, $state, $stateParams) {
+
+            $rootScope.$state = $state;
+            $rootScope.$stateParams = $stateParams;
+
+        }
+    ]).config(['$stateProvider', '$urlRouterProvider',
+        function($stateProvider, $urlRouterProvider) {
+
+            $urlRouterProvider.otherwise('/landingpage/table');
+
+            $stateProvider
+                .state('landingpage', {
+                    url: "/landingpage",
+                    controller: 'MainController',
+                    templateUrl: "views/main.html",
+                    data: { pageTitle: '首页', specialClass: 'landing-page' },
+                    resolve: {
+                        controller: ['$ocLazyLoad', function($ocLazyLoad) {
+                            return $ocLazyLoad.load([
+
+                            ])
+                        }]
+                    }
+                })
+                .state('landingpage.table', {
+                    url: "/table",
+                    controller: 'TableController',
+                    templateUrl: "views/commons/table.html",
+                    resolve: {
+                        controller: ['$ocLazyLoad', function($ocLazyLoad) {
+                            return $ocLazyLoad.load([
+                                // 'scripts/controllers/table.controller.js'
+
+
+                            ])
+                        }]
+                    }
+                })
+                .state('select', {
+                    url: '/select',
+                    templateUrl: "views/testcase.html",
+                    data: { specialClass: 'top-navigation' },
+
+                })
+                .state('select.selectTestCase', {
+                    url: "/selectCase",
+                    controller: 'CaseController',
+                    templateUrl: "views/commons/selectTestcase.html",
+
+                })
+                .state('select.testlist', {
+                    url: "/caselist",
+                    templateUrl: "views/commons/testCaseList.html"
+                })
+                .state('select.admin', {
+                    url: "/admin",
+                    controller: 'AdminController',
+                    templateUrl: "views/commons/admin.html"
+                })
+                .state('select.testVisual', {
+                    url: "/visual",
+                    controller: "testVisualController",
+                    templateUrl: "views/commons/testCaseVisual.html"
+                })
+                // .state('admin', {
+                //     url: '/admin',
+                //     data: { specialClass: ' fixed-sidebar  pace-done' },
+                //     templateUrl: "views/commons/admin.html"
+                // })
+
+        }
+    ])
+    .run();
diff --git a/utils/test/reporting/pages/app/scripts/controllers/admin.controller.js b/utils/test/reporting/pages/app/scripts/controllers/admin.controller.js
new file mode 100644 (file)
index 0000000..e6ecad0
--- /dev/null
@@ -0,0 +1,26 @@
+'use strict';
+
+/**
+ * @ngdoc function
+ * @name opnfvdashBoardAngularApp.controller:AdminController
+ * @description
+ * # TableController
+ * Controller of the opnfvdashBoardAngularApp
+ */
+angular.module('opnfvApp')
+    .controller('AdminController', ['$scope', '$state', '$stateParams', 'TableFactory', function($scope, $state, $stateParams, TableFactory) {
+
+        init();
+        $scope.showSelectValue = 0;
+        $scope.scenarioList = ["os_nosdn_kvm_noha", "os_nosdn_kvm_no", "os_nosdn_kvm_"];
+
+        function init() {}
+
+
+
+
+
+
+
+
+    }]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/controllers/case.controller.js b/utils/test/reporting/pages/app/scripts/controllers/case.controller.js
new file mode 100644 (file)
index 0000000..a4f1651
--- /dev/null
@@ -0,0 +1,70 @@
+'use strict';
+
+/**
+ * @ngdoc function
+ * @name opnfvdashBoardAngularApp.controller:CaseController
+ * @description
+ * # TableController
+ * Controller of the opnfvdashBoardAngularApp
+ */
+angular.module('opnfvApp')
+    .controller('CaseController', ['$scope', '$state', '$stateParams', 'TableFactory', function($scope, $state, $stateParams, TableFactory) {
+
+        init();
+        $scope.projectSelect = "";
+        $scope.funcTestCase = ['test1func', 'test2func', 'test3func', 'test4func'];
+        $scope.yardStickCase = ['test1yard', 'test2yard', 'test3yard', 'test4yard'];
+        $scope.bottleNeckCase = ['test1bottle', 'test2bottle', 'test3bottle', 'test4bottle',
+            'test5bottle', 'test6bottle', 'test7bottle', 'test8bottle',
+            'test9bottle', 'test10bottle', 'test11bottle', 'test12bottle',
+            'test13bottle', 'test14bottle', 'test15bottle', 'test16bottle',
+            'test17bottle', 'test18bottle', 'test19bottle', 'test20bottle'
+        ];
+        $scope.selectedFunc = ["test1func"];
+        $scope.selectBottle = ["test8bottle"];
+        $scope.versionlist = ["Colorado", "Master"];
+        $scope.VersionOption = [
+            { title: 'Colorado' },
+            { title: 'Master' }
+        ];
+        $scope.VersionConfig = {
+            create: true,
+            valueField: 'title',
+            labelField: 'title',
+            delimiter: '|',
+            maxItems: 1,
+            placeholder: 'Version',
+            onChange: function(value) {
+                checkElementArrayValue($scope.selection, $scope.VersionOption);
+                $scope.selection.push(value);
+                // console.log($scope.selection);
+
+            }
+        };
+
+
+        function init() {
+            $scope.toggleSelection = toggleSelection;
+            $scope.toggleSelectionMulti = toggleSelectionMulti;
+
+        }
+
+        function toggleSelection(status) {
+            // var idx = $scope.weekselection.indexOf(status);
+            $scope.projectSelect = status;
+
+        }
+
+        function toggleSelectionMulti(status) {
+            var idx = $scope.selection.indexOf(status);
+
+            if (idx > -1) {
+                $scope.selection.splice(idx, 1);
+            } else {
+                $scope.selection.push(status);
+            }
+            console.log($scope.selection);
+        }
+
+
+    }]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/controllers/main.controller.js b/utils/test/reporting/pages/app/scripts/controllers/main.controller.js
new file mode 100644 (file)
index 0000000..2054dc2
--- /dev/null
@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ * @ngdoc function
+ * @name opnfvdashBoardAngularApp.controller:MainPageController
+ * @description
+ * # TableController
+ * Controller of the opnfvdashBoardAngularApp
+ */
+angular.module('opnfvApp')
+    .controller('MainController', ['$scope', '$state', '$stateParams', function($scope, $state, $stateParams) {
+
+        init();
+
+        function init() {
+            $scope.goTest = goTest;
+            $scope.goLogin = goLogin;
+
+        }
+
+        function goTest() {
+            $state.go("select.selectTestCase");
+        }
+
+        function goLogin() {
+            $state.go("login");
+        }
+
+
+
+
+    }]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/controllers/table.controller.js b/utils/test/reporting/pages/app/scripts/controllers/table.controller.js
new file mode 100644 (file)
index 0000000..0f3a17a
--- /dev/null
@@ -0,0 +1,385 @@
+'use strict';
+
+/**
+ * @ngdoc function
+ * @name opnfvdashBoardAngularApp.controller:TableController
+ * @description
+ * # TableController
+ * Controller of the opnfvdashBoardAngularApp
+ */
+angular.module('opnfvApp')
+    .controller('TableController', ['$scope', '$state', '$stateParams', '$http', 'TableFactory', function($scope, $state, $stateParams, $http, TableFactory) {
+
+        $scope.filterlist = [];
+        $scope.selection = [];
+        $scope.statusList = [];
+        $scope.projectList = [];
+        $scope.installerList = [];
+        $scope.versionlist = [];
+        $scope.loopci = [];
+        $scope.time = [];
+        $scope.tableDataAll = {};
+        $scope.tableInfoAll = {};
+        $scope.scenario = {};
+
+        $scope.VersionConfig = {
+            create: true,
+            valueField: 'title',
+            labelField: 'title',
+            delimiter: '|',
+            maxItems: 1,
+            placeholder: 'Version',
+            onChange: function(value) {
+                checkElementArrayValue($scope.selection, $scope.VersionOption);
+                $scope.selection.push(value);
+                // console.log($scope.selection);
+                getScenarioData();
+
+            }
+        }
+
+        $scope.LoopConfig = {
+            create: true,
+            valueField: 'title',
+            labelField: 'title',
+            delimiter: '|',
+            maxItems: 1,
+            placeholder: 'Loop',
+            onChange: function(value) {
+                checkElementArrayValue($scope.selection, $scope.LoopOption);
+                $scope.selection.push(value);
+                // console.log($scope.selection);
+                getScenarioData();
+
+            }
+        }
+
+        $scope.TimeConfig = {
+            create: true,
+            valueField: 'title',
+            labelField: 'title',
+            delimiter: '|',
+            maxItems: 1,
+            placeholder: 'Time',
+            onChange: function(value) {
+                checkElementArrayValue($scope.selection, $scope.TimeOption);
+                $scope.selection.push(value);
+                // console.log($scope.selection)
+                getScenarioData();
+
+
+            }
+        }
+
+
+        init();
+
+        function init() {
+            $scope.toggleSelection = toggleSelection;
+            getScenarioData();
+            // radioSetting();
+            getFilters();
+        }
+
+        function getFilters() {
+            TableFactory.getFilter().get({
+
+
+            }).$promise.then(function(response) {
+                if (response != null) {
+                    $scope.statusList = response.filters.status;
+                    $scope.projectList = response.filters.projects;
+                    $scope.installerList = response.filters.installers;
+                    $scope.versionlist = response.filters.version;
+                    $scope.loopci = response.filters.loops;
+                    $scope.time = response.filters.time;
+
+                    $scope.statusListString = $scope.statusList.toString();
+                    $scope.projectListString = $scope.projectList.toString();
+                    $scope.installerListString = $scope.installerList.toString();
+                    $scope.VersionSelected = $scope.versionlist[1];
+                    $scope.LoopCiSelected = $scope.loopci[0];
+                    $scope.TimeSelected = $scope.time[0];
+                    radioSetting($scope.versionlist, $scope.loopci, $scope.time);
+
+                } else {
+                    alert("网络错误");
+                }
+            })
+        }
+
+        function getScenarioData() {
+
+            var utl = BASE_URL + '/scenarios';
+            var data = {
+                'status': ['success', 'danger', 'warning'],
+                'projects': ['functest', 'yardstick'],
+                'installers': ['apex', 'compass', 'fuel', 'joid'],
+                'version': $scope.VersionSelected,
+                'loops': $scope.LoopCiSelected,
+                'time': $scope.TimeSelected
+            };
+            var config = {
+                headers: {
+                    'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
+                }
+            }
+            $http.post(utl, data, config).then(function(response) {
+                if (response.status == 200) {
+                    $scope.scenario = response.data;
+                    constructJson();
+                }
+            })
+        }
+
+        //construct json 
+        function constructJson() {
+
+            var colspan;
+            var InstallerData;
+            var projectsInfo;
+            $scope.tableDataAll["scenario"] = [];
+
+
+            for (var item in $scope.scenario.scenarios) {
+
+                var headData = Object.keys($scope.scenario.scenarios[item].installers).sort();
+                var scenarioStatus = $scope.scenario.scenarios[item].status;
+                var scenarioStatusDisplay;
+                if (scenarioStatus == "success") {
+                    scenarioStatusDisplay = "navy";
+                } else if (scenarioStatus == "danger") {
+                    scenarioStatusDisplay = "danger";
+                } else if (scenarioStatus == "warning") {
+                    scenarioStatusDisplay = "warning";
+                }
+
+                InstallerData = headData;
+                var projectData = [];
+                var datadisplay = [];
+                var projects = [];
+
+                for (var j = 0; j < headData.length; j++) {
+
+                    projectData.push($scope.scenario.scenarios[item].installers[headData[j]]);
+                }
+                for (var j = 0; j < projectData.length; j++) {
+
+                    for (var k = 0; k < projectData[j].length; k++) {
+                        projects.push(projectData[j][k].project);
+                        var temArray = [];
+                        if (projectData[j][k].score == null) {
+                            temArray.push("null");
+                            temArray.push(projectData[j][k].project);
+                            temArray.push(headData[j]);
+                        } else {
+                            temArray.push(projectData[j][k].score);
+                            temArray.push(projectData[j][k].project);
+                            temArray.push(headData[j]);
+                        }
+
+
+                        if (projectData[j][k].status == "platinium") {
+                            temArray.push("primary");
+                            temArray.push("P");
+                        } else if (projectData[j][k].status == "gold") {
+                            temArray.push("danger");
+                            temArray.push("G");
+                        } else if (projectData[j][k].status == "silver") {
+                            temArray.push("warning");
+                            temArray.push("S");
+                        } else if (projectData[j][k].status == null) {
+                            temArray.push("null");
+                        }
+
+                        datadisplay.push(temArray);
+
+                    }
+
+                }
+
+                colspan = projects.length / headData.length;
+
+                var tabledata = {
+                    scenarioName: item,
+                    Installer: InstallerData,
+                    projectData: projectData,
+                    projects: projects,
+                    datadisplay: datadisplay,
+                    colspan: colspan,
+                    status: scenarioStatus,
+                    statusDisplay: scenarioStatusDisplay
+                };
+
+                JSON.stringify(tabledata);
+                $scope.tableDataAll.scenario.push(tabledata);
+
+                // console.log(tabledata);
+
+            }
+
+
+            projectsInfo = $scope.tableDataAll.scenario[0].projects;
+
+            var tempHeadData = [];
+
+            for (var i = 0; i < InstallerData.length; i++) {
+                for (var j = 0; j < colspan; j++) {
+                    tempHeadData.push(InstallerData[i]);
+                }
+            }
+
+            //console.log(tempHeadData);
+
+            var projectsInfoAll = [];
+
+            for (var i = 0; i < projectsInfo.length; i++) {
+                var tempA = [];
+                tempA.push(projectsInfo[i]);
+                tempA.push(tempHeadData[i]);
+                projectsInfoAll.push(tempA);
+
+            }
+            //console.log(projectsInfoAll);
+
+            $scope.tableDataAll["colspan"] = colspan;
+            $scope.tableDataAll["Installer"] = InstallerData;
+            $scope.tableDataAll["Projects"] = projectsInfoAll;
+
+            // console.log($scope.tableDataAll);
+            $scope.colspan = $scope.tableDataAll.colspan;
+
+        }
+
+        //get json element size
+        function getSize(jsondata) {
+            var size = 0;
+            for (var item in jsondata) {
+                size++;
+            }
+            return size;
+        }
+
+        $scope.colspan = $scope.tableDataAll.colspan;
+        // console.log($scope.colspan);
+
+
+        //find all same element index 
+        function getSameElementIndex(array, element) {
+            var indices = [];
+            var idx = array.indexOf(element);
+            while (idx != -1) {
+                indices.push(idx);
+                idx = array.indexOf(element, idx + 1);
+            }
+            //return indices;
+            var result = { element: element, index: indices };
+            JSON.stringify(result);
+            return result;
+        }
+
+        //delete element in array
+        function deletElement(array, index) {
+            array.splice(index, 1);
+
+        }
+
+        function radioSetting(array1, array2, array3) {
+            var tempVersion = [];
+            var tempLoop = [];
+            var tempTime = [];
+            for (var i = 0; i < array1.length; i++) {
+                var temp = {
+                    title: array1[i]
+                };
+                tempVersion.push(temp);
+            }
+            for (var i = 0; i < array2.length; i++) {
+                var temp = {
+                    title: array2[i]
+                };
+                tempLoop.push(temp);
+            }
+            for (var i = 0; i < array3.length; i++) {
+                var temp = {
+                    title: array3[i]
+                };
+                tempTime.push(temp);
+            }
+            $scope.VersionOption = tempVersion;
+            $scope.LoopOption = tempLoop;
+            $scope.TimeOption = tempTime;
+        }
+
+        //remove element in the array
+        function removeArrayValue(arr, value) {
+            for (var i = 0; i < arr.length; i++) {
+                if (arr[i] == value) {
+                    arr.splice(i, 1);
+                    break;
+                }
+            }
+        }
+
+        //check if exist element
+        function checkElementArrayValue(arrayA, arrayB) {
+            for (var i = 0; i < arrayB.length; i++) {
+                if (arrayA.indexOf(arrayB[i].title) > -1) {
+                    removeArrayValue(arrayA, arrayB[i].title);
+                }
+            }
+        }
+
+        function toggleSelection(status) {
+            var idx = $scope.selection.indexOf(status);
+
+            if (idx > -1) {
+                $scope.selection.splice(idx, 1);
+                filterData($scope.selection)
+            } else {
+                $scope.selection.push(status);
+                filterData($scope.selection)
+            }
+            // console.log($scope.selection);
+
+        }
+
+        //filter function
+        function filterData(selection) {
+
+            $scope.selectInstallers = [];
+            $scope.selectProjects = [];
+            $scope.selectStatus = [];
+            for (var i = 0; i < selection.length; i++) {
+                if ($scope.statusListString.indexOf(selection[i]) > -1) {
+                    $scope.selectStatus.push(selection[i]);
+                }
+                if ($scope.projectListString.indexOf(selection[i]) > -1) {
+                    $scope.selectProjects.push(selection[i]);
+                }
+                if ($scope.installerListString.indexOf(selection[i]) > -1) {
+                    $scope.selectInstallers.push(selection[i]);
+                }
+            }
+
+            $scope.colspan = $scope.selectProjects.length;
+            //when some selection is empty, we set it full
+            if ($scope.selectInstallers.length == 0) {
+                $scope.selectInstallers = $scope.installerList;
+
+            }
+            if ($scope.selectProjects.length == 0) {
+                $scope.selectProjects = $scope.projectList;
+                $scope.colspan = $scope.tableDataAll.colspan;
+            }
+            if ($scope.selectStatus.length == 0) {
+                $scope.selectStatus = $scope.statusList
+            }
+
+            // console.log($scope.selectStatus);
+            // console.log($scope.selectProjects);
+
+        }
+
+
+    }]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/controllers/testvisual.controller.js b/utils/test/reporting/pages/app/scripts/controllers/testvisual.controller.js
new file mode 100644 (file)
index 0000000..7082aed
--- /dev/null
@@ -0,0 +1,111 @@
+'use strict';
+
+/**
+ * @ngdoc function
+ * @name opnfvdashBoardAngularApp.controller:testVisualController
+ * @description
+ * # TableController
+ * Controller of the opnfvdashBoardAngularApp
+ */
+angular.module('opnfvApp')
+    .controller('testVisualController', ['$scope', '$state', '$stateParams', 'TableFactory', 'ngDialog', '$http', '$loading',
+        function($scope, $state, $stateParams, TableFactory, ngDialog, $http, $loading) {
+            $scope.dovet = "59,222,156,317";
+            $scope.functest = "203,163,334,365";
+            $scope.yardstick = "398,161,513,384";
+            $scope.vsperf = "567,163,673,350";
+            $scope.stor = "686,165,789,341";
+            $scope.qtip = "802,164,905,341";
+            $scope.bootleneck = "917,161,1022,338";
+            $scope.noPopArea1 = "30,11,1243,146";
+            $scope.noPopArea2 = "1041,157,1250,561";
+            $scope.noPopArea3 = "15,392,1027,561";
+
+            init();
+            $scope.showSelectValue = 0;
+            $scope.scenarioList = ["os_nosdn_kvm_noha", "os_nosdn_kvm_no", "os_nosdn_kvm_"];
+
+            function init() {
+                $scope.myTrigger = myTrigger;
+                $scope.openTestDetail = openTestDetail;
+                $scope.pop = pop;
+                $scope.getDetail = getDetail;
+                getUrl();
+
+
+
+            }
+
+            function myTrigger(name) {
+                $loading.start('Key');
+                $scope.tableData = null;
+                $scope.modalName = name;
+
+                var url = PROJECT_URL + '/projects/' + name + '/cases';
+
+                var config = {
+                    headers: {
+                        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
+                    }
+                }
+                $http.get(url, config).then(function(response) {
+                    if (response.status == 200) {
+                        $scope.tableData = response.data;
+                        $loading.finish('Key');
+
+
+                    }
+                })
+            }
+
+            function getDetail(casename) {
+                TableFactory.getProjectTestCaseDetail().get({
+                    'project': $scope.modalName,
+                    'testcase': casename
+                }).$promise.then(function(response) {
+                    if (response != null) {
+                        $scope.project_name_modal = response.project_name;
+                        $scope.description_modal = response.description;
+                        openTestDetail();
+                    }
+                })
+
+            }
+
+
+            function openTestDetail() {
+                ngDialog.open({
+                    template: 'views/modal/testcasedetail.html',
+                    className: 'ngdialog-theme-default',
+                    scope: $scope,
+                    showClose: false
+                })
+            }
+
+            function getUrl() {
+                TableFactory.getProjectUrl().get({
+
+                }).$promise.then(function(response) {
+                    if (response != null) {
+                        $scope.functesturl = response.functest;
+                        $scope.yardstickurl = response.yardstick;
+                        $scope.vsperfurl = response.vsperf;
+                        $scope.storperfurl = response.storperf;
+                        $scope.qtipurl = response.qtip;
+                        $scope.bottlenecksurl = response.bottlenecks;
+                        $scope.doveturl = null;
+                    }
+                })
+            }
+
+
+
+
+
+
+
+
+
+
+        }
+    ]);
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/scripts/factory/table.factory.js b/utils/test/reporting/pages/app/scripts/factory/table.factory.js
new file mode 100644 (file)
index 0000000..2a8cbd0
--- /dev/null
@@ -0,0 +1,48 @@
+'use strict';
+
+/**
+ * get data factory
+ */
+angular.module('opnfvApp')
+    .factory('TableFactory', function($resource, $rootScope) {
+
+        return {
+            getFilter: function() {
+                return $resource(BASE_URL + '/filters', {}, {
+                    'get': {
+                        method: 'GET',
+
+                    }
+                });
+            },
+            getScenario: function() {
+                return $resource(BASE_URL + '/scenarios', {}, {
+                    'post': {
+                        method: 'POST',
+                    }
+                })
+            },
+            getProjectUrl: function() {
+                return $resource(PROJECT_URL + '/projects-page/projects', {}, {
+                    'get': {
+                        method: 'GET'
+                    }
+                })
+            },
+            getProjectTestCases: function() {
+                return $resource(PROJECT_URL + '/projects/:project/cases', { project: '@project' }, {
+                    'get': {
+                        method: 'GET'
+                    }
+                })
+            },
+            getProjectTestCaseDetail: function() {
+                return $resource(PROJECT_URL + '/projects/:project/cases/:testcase', { project: '@project', testcase: '@testcase' }, {
+                    'get': {
+
+                        method: 'GET'
+                    }
+                })
+            }
+        };
+    });
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/styles/custome.css b/utils/test/reporting/pages/app/styles/custome.css
new file mode 100644 (file)
index 0000000..7ab869b
--- /dev/null
@@ -0,0 +1,101 @@
+.container-tablesize {
+    margin: auto 5%;
+}
+
+.btn-outline {
+    border-color: white;
+}
+
+.chosen-container-single .chosen-single {
+    background: #ffffff;
+    box-shadow: none;
+    -moz-box-sizing: border-box;
+    background-color: #FFFFFF;
+    border: 1px solid #CBD5DD;
+    border-radius: 2px;
+    cursor: text;
+    height: 25px;
+    margin: 0;
+    min-height: 30px;
+    overflow: hidden;
+    padding: 4px 12px;
+    position: relative;
+    width: 100%;
+}
+
+.selectize-input {
+    height: 30px;
+    width: 100px;
+}
+
+.myhr {
+    border: 0.5px dashed #e7eaec;
+    border-top: 1px;
+    margin-bottom: 3px;
+}
+
+td.null {
+    background-color: #e7eaec;
+    color: #e7eaec;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"].disabled,
+input[type="checkbox"].disabled,
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"] {
+    cursor: not-allowed;
+    display: none;
+}
+
+body,
+html {
+    margin: 0;
+    padding: 0;
+    min-height: 100%;
+}
+
+
+/*img[usemap] {
+    border: none;
+    height: auto;
+    max-width: 100%;
+    width: auto;
+}*/
+
+.popup {
+    position: absolute;
+    display: none;
+    /*background-color: #dd8;*/
+    border-radius: 5px 5px 5px 5px;
+    background-color: #f3f3f4;
+    opacity: 0.9;
+}
+
+.ngdialog.ngdialog.ngdialog-theme-default .ngdialog-content {
+    background-color: #ffffff;
+    border-radius: 5px;
+}
+
+
+/*
+body {
+    height: 1200px;
+}
+
+html {
+    min-height: 100%;
+}*/
+
+
+/*html,
+body {
+    height: 100%;
+}
+
+#page-wrapper {
+    position: inherit;
+    margin: 0 0 0 220px;
+    min-height: 773px;
+}*/
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/commons/admin.html b/utils/test/reporting/pages/app/views/commons/admin.html
new file mode 100644 (file)
index 0000000..25911ac
--- /dev/null
@@ -0,0 +1,35 @@
+<div class="row">
+    <div classs="container">
+        <div class="col-lg-12">
+            <div class="ibox float-e-margins">
+                <div class="ibox-content">
+
+                    <div>
+                        <h3 class="font-bold no-margins">
+                            My Scenarios
+                        </h3>
+                        <small>List all your Scenarios.</small>
+                    </div>
+                    <hr>
+                    <table class="table table-striped">
+                        <tbody>
+                            <tr ng-init="count=1" ng-click="showSelect(count)" ng-repeat=" list in scenarioList">
+
+                                <td> {{list}}</td>
+                                <td> <input type="checkbox"></i>
+                                </td>
+
+                            </tr>
+                        </tbody>
+                    </table>
+
+                </div>
+
+            </div>
+        </div>
+    </div>
+    <div style="margin-top: 10px;" class="pull-right">
+        <button class="btn btn-primary" ng-click="test()">Create</button>
+        <button class="btn btn-primary" ng-click="test()">Delete</button>
+    </div>
+</div>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/commons/selectTestcase.html b/utils/test/reporting/pages/app/views/commons/selectTestcase.html
new file mode 100644 (file)
index 0000000..4c9e2b3
--- /dev/null
@@ -0,0 +1,142 @@
+<!--select page-->
+<div class="row">
+    <div classs="container">
+        <div class="col-lg-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-content">
+                    <div>
+                        <!--<span class="pull-right text-right">
+                                        <small>Average value of sales in the past month in: <strong>United states</strong></small>
+                                            <br/>
+                                            All sales: 162,862
+                                        </span>-->
+                        <h3 class="font-bold no-margins">
+                            Select Project
+                        </h3>
+                        <small>Choose a Project to Select Test cases.</small>
+                        <div class="pull-right">
+                            <selectize options="VersionOption" ng-model="VersionSelected" config="VersionConfig"></selectize>
+                        </div>
+                    </div>
+                    <hr>
+
+                    <div class="m-t-sm">
+
+                        <div class="row">
+
+                            <center>
+                                <div data-toggle="buttons" class="m-t-sm">
+                                    <label class="btn btn-outline btn-success " style=" margin-right: 5px;" ng-click="toggleSelection('FuncTest')">
+                              <input type="radio"  disabled="disabled"  >FuncTest
+                            
+                                </label>
+                                    <label class="btn btn-outline btn-success " style=" margin-right: 5px;" ng-click="toggleSelection('YardStick')">
+                              <input type="radio"  disabled="disabled" > YardStick
+                            
+                                </label>
+                                    <label class="btn btn-outline btn-success " style=" margin-right: 5px;" ng-click="toggleSelection('Bottleneck')">
+                              <input type="radio"  disabled="disabled" > Bottleneck
+                            
+                                </label>
+                                </div>
+                            </center>
+
+                        </div>
+
+                    </div>
+
+                </div>
+            </div>
+        </div>
+        <div class="col-lg-4">
+            <div class="ibox float-e-margins">
+                <div class="ibox-content">
+                    <div>
+                        <!--<span class="pull-right text-right">
+                                        <small>Average value of sales in the past month in: <strong>United states</strong></small>
+                                            <br/>
+                                            All sales: 162,862
+                                        </span>-->
+                        <h3 class="font-bold no-margins">
+                            Project Description
+
+                        </h3>
+
+
+
+                    </div>
+                    <hr>
+                    <div class="m-t-sm" ng-show="projectSelect.indexOf('YardStick')!=-1">
+                        <p>
+                            YardStick is a someting.......
+                        </p>
+                    </div>
+                    <div class="m-t-sm" ng-show="projectSelect.indexOf('FuncTest')!=-1">
+                        <p>
+                            FuncTest is a someting.......
+                        </p>
+                    </div>
+                    <div class="m-t-sm" ng-show="projectSelect.indexOf('Bottleneck')!=-1">
+                        <p>
+                            Bottleneck is a someting.......
+                        </p>
+                    </div>
+
+
+                </div>
+            </div>
+
+        </div>
+
+        <div class="col-lg-12">
+            <div class="ibox float-e-margins">
+                <div class="ibox-content">
+                    <div>
+                        <!--<span class="pull-right text-right">
+                                        <small>Average value of sales in the past month in: <strong>United states</strong></small>
+                                            <br/>
+                                            All sales: 162,862
+                                        </span>-->
+                        <h3 class="font-bold no-margins">
+                            Select Test Case
+                        </h3>
+                        <small>Choose Test Cases </small>
+
+                    </div>
+                    <hr>
+
+                    <div class="m-t-sm">
+                        <div data-toggle="buttons" class="m-t-sm" ng-show="projectSelect.indexOf('FuncTest')!=-1">
+                            <label class="btn btn-outline btn-success " style=" margin-right: 5px;" tooltips tooltip-template="FuncTest is something" tooltip-size="small" ng-repeat="case in funcTestCase" value={{case}} ng-class="{'active': case == selectedFunc}">
+                              <input type="checkbox"  disabled="disabled"  >{{case}}
+                            
+                                </label>
+                        </div>
+                        <div data-toggle="buttons" class="m-t-sm" ng-show="projectSelect.indexOf('YardStick')!=-1">
+                            <label class="btn btn-outline btn-success " style=" margin-right: 5px;" tooltips tooltip-template="FuncTest is something" tooltip-size="small" ng-repeat="case in yardStickCase" value={{case}}>
+                              <input type="checkbox"  disabled="disabled"  >{{case}}
+                            
+                                </label>
+                        </div>
+                        <div data-toggle="buttons" class="m-t-sm" ng-show="projectSelect.indexOf('Bottleneck')!=-1">
+                            <label class="btn btn-outline btn-success " style=" margin-right: 5px;" tooltips tooltip-template="FuncTest is something" tooltip-size="small" ng-repeat="case in bottleNeckCase" value={{case}} ng-class="{'active': case == selectBottle}">
+                              <input type="checkbox"  disabled="disabled"  >{{case}}
+                            
+                                </label>
+                        </div>
+
+                    </div>
+
+                </div>
+
+
+            </div>
+
+        </div>
+    </div>
+    <div style="margin-top: 10px;" class="pull-right">
+        <button class="btn btn-primary" ng-click="test()">Submit</button>
+    </div>
+
+
+</div>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/commons/table.html b/utils/test/reporting/pages/app/views/commons/table.html
new file mode 100644 (file)
index 0000000..f504bd7
--- /dev/null
@@ -0,0 +1,113 @@
+<section class="container-tablesize">
+    <div class="row  border-bottom white-bg dashboard-header">
+        <div class="row">
+
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>Reporting </h5>
+                    <div class="ibox-tools">
+                        <a class="collapse-link">
+                            <i class="fa fa-chevron-up"></i>
+                        </a>
+                        <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                            <i class="fa fa-wrench"></i>
+                        </a>
+                        <ul class="dropdown-menu dropdown-user">
+                            <li><a href="#">Config option 1</a>
+                            </li>
+                            <li><a href="#">Config option 2</a>
+                            </li>
+                        </ul>
+                        <a class="close-link">
+                            <i class="fa fa-times"></i>
+                        </a>
+                    </div>
+                </div>
+
+                <div class="ibox-content row">
+
+                    <div class=" col-md-12" data-toggle="buttons" aria-pressed="false">
+
+                        <label> Status </label> &nbsp;&nbsp; &nbsp;
+                        <label class="btn btn-outline btn-success btn-sm" style="height:25px; margin-right: 5px;" ng-repeat="status in statusList" value={{status}} ng-checked="selection.indexOf(status)>-1" ng-click="toggleSelection(status)">
+                              <input type="checkbox"  disabled="disabled" > {{status}}
+                            
+                          </label>
+                    </div>
+
+                    <hr class="myhr">
+
+                    <div class=" col-md-12" data-toggle="buttons">
+                        <label> Projects </label> &nbsp;
+                        <label class="btn btn-outline btn-success btn-sm " style="height:25px;margin-right: 5px;" ng-repeat="project in projectList" value={{project}} ng-checked="selection.indexOf(project)>-1" ng-click="toggleSelection(project)">
+                            <input type="checkbox" disabled="disabled"> {{project}}
+                        </label>
+
+                    </div>
+                    <hr class="myhr">
+                    <div class=" col-md-12" data-toggle="buttons">
+                        <label> Installers </label>
+                        <label class="btn btn-outline btn-success btn-sm" style="height:25px;margin-right: 5px;" ng-repeat="installer in installerList" value={{installer}} ng-checked="selection.indexOf(installer)>-1" ng-click="toggleSelection(installer)">
+                            <input type="checkbox" disabled="disabled"> {{installer}}
+                            </label>
+                    </div>
+
+                    <hr style="border:0.5px dashed #e7eaec;border-top:1px;margin-bottom:10px;">
+
+
+                    <div class=" col-md-1" style="margin-top:5px;margin-right: 5px;">
+                        <selectize options="VersionOption" ng-model="VersionSelected" config="VersionConfig"></selectize>
+
+                    </div>
+
+                    <div class=" col-md-1" style="margin-top:5px;margin-right: 5px;">
+                        <selectize options="LoopOption" ng-model="LoopCiSelected" config="LoopConfig"></selectize>
+
+                    </div>
+
+                    <div class=" col-md-1" style="margin-top:5px;margin-right: 5px;">
+                        <selectize options="TimeOption" ng-model="TimeSelected" config="TimeConfig"></selectize>
+                    </div>
+                </div>
+                <div class="table-responsive">
+
+                    <table class="table table-bordered" id="table" ng-model="tableDataAll">
+                        <thead class="thead">
+                            <tr>
+                                <th>Scenario </th>
+                                <th colspan={{colspan}} ng-show="selectInstallers.indexOf(key)!=-1" value={{key}} ng-repeat="key in tableDataAll.Installer"><a href="notfound.html">{{key}}</a> </th>
+                            </tr>
+
+                            <tr>
+
+                                <td></td>
+                                <td ng-show="selectProjects.indexOf(project[0])!=-1 && selectInstallers.indexOf(project[1])!=-1" ng-repeat="project in tableDataAll.Projects track by $index" data={{project[1]}} value={{project[0]}}>{{project[0]}}</td>
+
+                            </tr>
+                        </thead>
+                        <tbody class="tbody">
+                            <tr ng-repeat="scenario in tableDataAll.scenario" ng-show="selectStatus.indexOf(scenario.status)!=-1">
+
+                                <td nowrap="nowrap" data={{scenario.status}}><span class="fa fa-circle text-{{scenario.statusDisplay}}"></span> <a href="notfound.html">{{scenario.scenarioName}}</a> </td>
+
+                                <!--<td style="background-color:#e7eaec" align="justify" ng-if="data[0]=='Not Support'" ng-repeat="data in scenario.datadisplay track by $index" data={{data[1]}} value={{data[2]}}></td>-->
+
+                                <td nowrap="nowrap" ng-show="selectInstallers.indexOf(data[2])!=-1 && selectProjects.indexOf(data[1])!=-1" ng-repeat="data in scenario.datadisplay track by $index" data={{data[1]}} value={{data[2]}} class={{data[0]}}>
+                                    <span class="label label-{{data[3]}}">{{data[4]}}</a></span> {{data[0]}}</td>
+
+
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+
+                <div class="pull-right" style="margin-top: 5px">
+                    <span class="label label-danger">G</span>gold<span style="padding-left:20px"></span>
+                    <span class="label label-primary">P</span><span>platinium</span><span style="padding-left:20px"></span>
+                    <span class="label label-warning">S</span><span>silver</span>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</section>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/commons/testCaseList.html b/utils/test/reporting/pages/app/views/commons/testCaseList.html
new file mode 100644 (file)
index 0000000..0a28b6c
--- /dev/null
@@ -0,0 +1,55 @@
+<div class="col-lg-12">
+    <div class="ibox float-e-margins">
+        <div class="ibox-content">
+            <div>
+                <h3 class="font-bold no-margins">
+                    Test Case List
+                </h3>
+                <small>list for test case </small>
+            </div>
+            <hr>
+
+            <table class="table table-striped">
+                <thead>
+                    <tr>
+                        <th>Scenario Name</th>
+                        <th>Owner</th>
+                        <th>Description</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr>
+                        <td> os_nosdn_kvm_noha</td>
+                        <td>username</td>
+                        <td>balalalalala</td>
+                    </tr>
+                    <tr>
+                        <td> os_nosdn_kvm_</td>
+                        <td>username</td>
+                        <td>balalalalala</td>
+                    </tr>
+                    <tr>
+                        <td> os_nosdn_kvm_noha</td>
+                        <td>username</td>
+                        <td>balalalalala</td>
+                    </tr>
+
+                </tbody>
+
+
+
+
+
+            </table>
+
+
+
+
+
+
+
+
+
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/commons/testCaseVisual.html b/utils/test/reporting/pages/app/views/commons/testCaseVisual.html
new file mode 100644 (file)
index 0000000..9d146ba
--- /dev/null
@@ -0,0 +1,126 @@
+<!--select page-->
+
+
+<div class="row">
+
+
+    <div class="row  border-bottom white-bg dashboard-header" style="border-radius: 5px 5px 5px 5px ">
+
+        <h3>OPNFV Test ecosystem
+            <small> *mouse over display test case list</small>
+        </h3>
+        <p>There are several projects dealing with integration and testing</p>
+
+        <div>
+            <img src="images/overview.png" usemap="#overview" class="img-responsive">
+            <map name="overview">
+                <area shape="rect" coords={{dovet}} alt="test" href="{{doveturl}}" onmouseover="pop(event)" ng-mouseover="myTrigger('dovetail')"/>
+               <area shape="rect" coords={{functest}} alt="test" href="{{functesturl}}"  onmouseover="pop(event)" ng-mouseover="myTrigger('functest')" />
+                <area shape="rect" coords={{yardstick}} alt="test" href="{{yardstickurl}}" onmouseover="pop(event)" ng-mouseover="myTrigger('yardstick')"/>
+                 <area shape="rect" coords={{vsperf}} alt="test" href="{{vsperfurl}}" onmouseover="pop(event)" ng-mouseover="myTrigger('vsperf')" />
+                  <area shape="rect" coords={{stor}} alt="test" href="{{storperfurl}}" onmouseover="pop(event)" ng-mouseover="myTrigger('storperf')"/>
+                   <area shape="rect" coords={{qtip}} alt="test" href="{{qtipurl}}"  onmouseover="pop(event)" ng-mouseover="myTrigger('qtip')" />
+                    <area shape="rect" coords={{bootleneck}} alt="test"  href="{{bottlenecksurl}}" onmouseover="pop(event)" ng-mouseover="myTrigger('bootlenecks')" />
+                     <area shape="rect" coords={{noPopArea1}} alt="test" onmouseover="pophide(event)"  />
+                      <area shape="rect" coords={{noPopArea2}} alt="test"  onmouseover="pophide(event)"  />
+                       <area shape="rect" coords={{noPopArea3}} alt="test"  onmouseover="pophide(event)"  />
+                
+           </map>
+
+        </div>
+    </div>
+    <div style="display: inline-block;">
+        <div class="row  border-bottom white-bg dashboard-header" style="border-radius: 5px 5px 5px 5px;margin-right:30px;margin-top:10px;display:inline-block; ">
+            <h4>Introduction</h4>
+            <p>
+                Testing is still a key challenge for OPNFV.
+            </p>
+            <p>
+                All the projects must manage their test strategy (unit, functional, security, performance)
+            </p>
+            <p>
+                Several specific test projects have been validated by TSC and already deal with:
+                <ul>
+                    <li>Define testcases</li>
+                    <li>Perform tests not covered by a single project</li>
+                    <li>Create tooling</li>
+                    <li>Study Performance end to end</li>
+                </ul>
+            </p>
+
+
+        </div>
+        <div class="row  border-bottom white-bg dashboard-header" style="border-radius: 5px 5px 5px 5px;margin-top:10px;display:inline-block;">
+            <h4>Project details</h4>
+
+            We consider the projects referenced on the wiki main page:
+            <ul>
+                <li>Functest: VIM and NFVI funcionnal testing Umbrella project for functional testing</li>
+                <li>Yardstick: Verification of the infrastructure compliance when running VNF applications.
+                    <br>Umbrella project for performance testing</li>
+                <li>Storperf: Storage Perfomance testing</li>
+                <li>VSperf: Data-plane performance testing</li>
+                <li>CPerf: Controller performance testing</li>
+                <li>Bottlenecks:Detect Bottlenecks in OPNFV solution</li>
+                <li>QTIP: Platform Performance Benchmarking</li>
+                <li>Dovetail: Test OPNFV validation criteria for use of OPNFV trademarks</li>
+            </ul>
+            </p>
+        </div>
+    </div>
+
+
+    <div id="popup" class="popup" style="width: 20%;height: 35%" dw-loading="Key">
+
+        <div ng-show="tableData.length==0">
+            <center>
+                <h3> No Data </h3>
+            </center>
+        </div>
+        <div ng-show="tableData.length!=0">
+
+            <table class="table">
+                <thead>
+                    <tr>
+                        <td>
+                            <h3>Name</h3>
+                        </td>
+                    </tr>
+                </thead>
+
+                <tbody>
+                    <tr dir-paginate="data in tableData | itemsPerPage: 8  track by $index ">
+                        <td><a ng-click="getDetail(data)"> {{data}}</a></td>
+                        <tr>
+
+                </tbody>
+
+            </table>
+
+            <center>
+                <dir-pagination-controls></dir-pagination-controls>
+            </center>
+        </div>
+
+    </div>
+
+
+    <script>
+        $(document).ready(function(e) {
+            $('img[usemap]').rwdImageMaps();
+
+        });
+
+        function pop(e) {
+            var thing = document.getElementById("popup");
+            thing.style.left = e.clientX + 'px';
+            thing.style.top = e.clientY + 'px';
+            setTimeout('$("#popup").show()', 1000);
+            return true;
+        }
+
+        function pophide(e) {
+            $('#popup').hide();
+            return true;
+        }
+    </script>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/main.html b/utils/test/reporting/pages/app/views/main.html
new file mode 100644 (file)
index 0000000..6c53dc0
--- /dev/null
@@ -0,0 +1,231 @@
+<div class="navbar-wrapper">
+    <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
+        <div class="container">
+            <div class="navbar-header page-scroll">
+                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+                        <span class="sr-only">Toggle navigation</span>
+                        <span class="icon-bar"></span>
+                        <span class="icon-bar"></span>
+                        <span class="icon-bar"></span>
+                    </button>
+                <a class="navbar-brand" href="index.html">OPNFV-DASHBOARD</a>
+            </div>
+            <div class="navbar-collapse collapse">
+                <ul class="nav navbar-nav navbar-right">
+                    <li><a href="#page-top">Home</a></li>
+                    <li><a href="#DashBoard">DashBoard</a></li>
+                    <li><a ui-sref="select.selectTestCase">TestCase</a></li>
+                    <li><a ui-sref="select.testVisual">catalogue page</a></li>
+                    <li><a ui-sref="login">Login</a></li>
+                    <!--<li><a href="#team">Team</a></li>
+                        <li><a href="#testimonials">Testimonials</a></li>
+                        <li><a href="#pricing">Pricing</a></li>
+                        <li><a href="#contact">Contact</a></li>-->
+                </ul>
+            </div>
+        </div>
+    </nav>
+</div>
+
+
+<div id="inSlider" class="carousel carousel-fade" data-ride="carousel">
+    <ol class="carousel-indicators">
+        <!--<li data-target="#inSlider" data-slide-to="0" class="active"></li>
+        <li data-target="#inSlider" data-slide-to="1"></li>-->
+    </ol>
+    <div class="carousel-inner" role="listbox">
+        <div class="item active">
+            <div class="container">
+                <div class="carousel-caption">
+                    <h1>OPNFV<br/> facilitates the development and evolution<br/> of NFV components across<br/> various open source ecosystems<br/>
+                    </h1>
+                    <!--<p>Lorem Ipsum is simply dummy text of the printing.</p>
+                    <p>-->
+                    <a class="btn btn-lg btn-primary" href="#" role="button">READ MORE</a>
+                    <!--<a class="caption-link" href="#" role="button">Inspinia Theme</a>-->
+                    <!--</p>-->
+                </div>
+
+            </div>
+            <!-- Set background for slide in css -->
+            <div class="header-back one" style="background: url('images/header_one.jpg') 50% 0 no-repeat;"></div>
+
+        </div>
+
+    </div>
+
+</div>
+
+<section id="DashBoard" class="container services">
+    <div class="row">
+
+        <h1>
+            OPNFV’s goals are to:
+        </h1>
+        <div class="col-sm-3">
+
+            <p>Develop an integrated and tested open source platform that can be used to build NFV functionality--accelerating the introduction of new products and services</p>
+            <p><a class="navy-link" href="#" role="button">Details &raquo;</a></p>
+        </div>
+        <div class="col-sm-3">
+
+            <p>Include participation of leading end users to validate that OPNFV meets the needs of user community</p>
+            <p><a class="navy-link" href="#" role="button">Details &raquo;</a></p>
+        </div>
+        <div class="col-sm-3">
+
+            <p>Contribute to and participate in relevant open source projects that will be leveraged in the OPNFV platform; ensuring consistency, performance and interoperability among open source components</p>
+            <p><a class="navy-link" href="#" role="button">Details &raquo;</a></p>
+        </div>
+        <div class="col-sm-3">
+
+            <p>Establish an ecosystem for NFV solutions based on open standards and software to meet the needs of end users</p>
+            <p><a class="navy-link" href="#" role="button">Details &raquo;</a></p>
+        </div>
+        <div class="col-sm-3">
+
+            <p>Promote OPNFV as the preferred platform and community for open source NFV</p>
+            <p><a class="navy-link" href="#" role="button">Details &raquo;</a></p>
+        </div>
+    </div>
+</section>
+
+
+<div ui-view></div>
+
+<section id="contact" class="gray-section contact" style="background-image: url(images/word_map.png)">
+    <div class="container">
+        <div class="row m-b-lg">
+            <div class="col-lg-12 text-center">
+                <div class="navy-line"></div>
+                <h1>Contact Us</h1>
+            </div>
+        </div>
+        <div class="row m-b-lg">
+            <div class="col-lg-3 col-lg-offset-3">
+                <address>
+          <strong><span class="navy">Press, Analyst, or Speaking Inquiries</span></strong><br/> pr@opnfv.org
+          <br/>
+        </address>
+                <address>
+          <strong><span class="navy">OPNFV Events</span></strong><br/> events@opnfv.org
+          <br/>
+        </address>
+                <address>
+          <strong><span class="navy">IT Support</span></strong><br/>opnfv-helpdesk@rt.linuxfoundation.org
+          <br/>
+        </address>
+            </div>
+
+            <div class="col-lg-4">
+                <address>
+          <strong><span class="navy">To submit and track bugs related to OPNFV</span></strong><br/>Please visit https://jira.opnfv.org
+          <br/>
+        </address>
+                <address>
+          <strong><span class="navy">Newsletter</span></strong><br/>Sign up for the OPNFV newsletter
+          <br/>
+        </address>
+                <address>
+          <strong><span class="navy">Membership</span></strong><br/>Please visit the Join as a Member page
+          <br/>
+        </address>
+
+            </div>
+        </div>
+        <div class="row">
+            <div class="col-lg-12 text-center">
+                <img src="images/logo.png" />
+
+            </div>
+        </div>
+        <div class="row">
+            <div class="col-lg-8 col-lg-offset-2 text-center m-t-lg m-b-lg">
+                <p><strong>&copy; 2016 Open Platform for NFV Project, Inc</strong><br/> A Linux Foundation Collaborative Project. All Rights Reserved. Open Platform for NFV and OPNFV are trademarks of the Open Platform for NFV Project, Inc. Linux Foundation
+                    is a registered trademark of The Linux Foundation. Linux is a registered trademark of Linus Torvalds. Please see our terms of use, trademark policy, and privacy policy.
+                </p>
+            </div>
+        </div>
+    </div>
+</section>
+
+<script>
+    $(document).ready(function() {
+
+        // $('body').scrollspy({
+        //     target: '.navbar-fixed-top',
+        //     offset: 80
+        // });
+
+        //Page scrolling feature
+        $('a.page-scroll').bind('click', function(event) {
+            var link = $(this);
+            $('html, body').stop().animate({
+                scrollTop: $(link.attr('href')).offset().top - 50
+            }, 500);
+            event.preventDefault();
+            $("#navbar").collapse('hide');
+        });
+
+        $('a.page-scroll').bind('click', function(event) {
+            var link = $(this);
+            $('html, body').stop().animate({
+                scrollTop: $(link.attr('href')).offset().top - 50
+            }, 500);
+            event.preventDefault();
+            $("#navbar").collapse('hide');
+        });
+
+    });
+
+    var config = {
+        '.chosen-select': {},
+        '.chosen-select-deselect': {
+            allow_single_deselect: true
+        },
+        '.chosen-select-no-single': {
+            disable_search_threshold: 10
+        },
+        '.chosen-select-no-results': {
+            no_results_text: 'Oops, nothing found!'
+        },
+        '.chosen-select-width': {
+            width: "95%"
+        }
+    }
+    for (var selector in config) {
+        $(selector).chosen(config[selector]);
+    }
+
+    var cbpAnimatedHeader = (function() {
+        var docElem = document.documentElement,
+            header = document.querySelector('.navbar-default'),
+            didScroll = false,
+            changeHeaderOn = 200;
+
+        function init() {
+            window.addEventListener('scroll', function(event) {
+                if (!didScroll) {
+                    didScroll = true;
+                    setTimeout(scrollPage, 250);
+                }
+            }, false);
+        }
+
+        function scrollPage() {
+            var sy = scrollY();
+            if (sy >= changeHeaderOn) {
+                $(header).addClass('navbar-scroll')
+            } else {
+                $(header).removeClass('navbar-scroll')
+            }
+            didScroll = false;
+        }
+
+        function scrollY() {
+            return window.pageYOffset || docElem.scrollTop;
+        }
+        init();
+
+    })();
+</script>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/modal/testcasedetail.html b/utils/test/reporting/pages/app/views/modal/testcasedetail.html
new file mode 100644 (file)
index 0000000..8918b3f
--- /dev/null
@@ -0,0 +1,7 @@
+<h4>Test Case Detail</h4>
+<div class="hr-line-dashed"></div>
+
+
+<strong> name</strong>: {{project_name_modal}}<br>
+
+<strong>description</strong>: {{description_modal}}<br>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/app/views/testcase.html b/utils/test/reporting/pages/app/views/testcase.html
new file mode 100644 (file)
index 0000000..0a4f266
--- /dev/null
@@ -0,0 +1,94 @@
+<div id="wrapper" style="min-height:100%;">
+    <div id="page-wrapper" class="gray-bg">
+        <div class="row border-bottom white-bg">
+            <nav class="navbar navbar-static-top" role="navigation">
+                <div class="navbar-header">
+                    <button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
+                    <i class="fa fa-reorder"></i>
+                </button>
+                    <a href="#" class="navbar-brand">OPNFV-TESTCASE</a>
+                </div>
+                <div class="navbar-collapse collapse" id="navbar">
+                    <ul class="nav navbar-nav">
+                        <!--<li class="active">
+                            <a aria-expanded="false" role="button" href="layouts.html"> Back to main Layout page</a>
+                        </li>-->
+                        <li class="dropdown" ng-class="{active: $state.includes('select.selectTestCase')}">
+                            <a aria-expanded="false" role="button" ui-sref="select.selectTestCase" data-toggle="dropdown"> Test Case Selection</a>
+                            <!--<ul role="menu" class="dropdown-menu">
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                            </ul>-->
+                        </li>
+                        <li class="dropdown" ng-class="{active: $state.includes('select.testlist')}">
+                            <a aria-expanded="false" role="button" ui-sref="select.testlist" data-toggle="dropdown"> Test Case List</a>
+                        </li>
+                        <li class="dropdown" ng-class="{active: $state.includes('select.admin')}">
+                            <a aria-expanded="false" role="button" data-toggle="dropdown"> Owner Center  <span class="caret"></span></a>
+                            <ul role="menu" class="dropdown-menu">
+                                <li ng-class="{active:$state.includes('select.admin')}"><a ui-sref="select.admin">My Scenarios</a></li>
+                                <li><a href="">others</a></li>
+                                <!--<li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>-->
+                            </ul>
+                        </li>
+
+                        <li class="dropdown" ng-class="{active: $state.includes('select.testVisual')}">
+                            <a aria-expanded="false" role="button" ui-sref="select.testVisual" data-toggle="dropdown"> Catalogue Page</a>
+                        </li>
+                        <li class="dropdown" ng-class="{active: $state.includes('landingpage.table')}">
+                            <a aria-expanded="false" role="button" ui-sref="landingpage.table" data-toggle="dropdown"> Landing Page</a>
+                        </li>
+
+                        <!--<li class="dropdown">
+                            <a aria-expanded="false" role="button" href="#" class="dropdown-toggle" data-toggle="dropdown"> Menu item <span class="caret"></span></a>
+                            <ul role="menu" class="dropdown-menu">
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                            </ul>
+                        </li>
+                        <li class="dropdown">
+                            <a aria-expanded="false" role="button" href="#" class="dropdown-toggle" data-toggle="dropdown"> Menu item <span class="caret"></span></a>
+                            <ul role="menu" class="dropdown-menu">
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                                <li><a href="">Menu item</a></li>
+                            </ul>
+                        </li>-->
+
+                    </ul>
+                    <ul class="nav navbar-top-links navbar-right">
+                        <li>
+                            <a ui-sref="login">
+                                <i class="fa fa-sign-in"></i> Log in
+                            </a>
+                        </li>
+                    </ul>
+                </div>
+            </nav>
+        </div>
+        <div class="wrapper wrapper-content">
+
+            <div ui-view></div>
+
+        </div>
+        <div class="footer">
+            <!--<div class="pull-right">
+                10GB of <strong>250GB</strong> Free.
+            </div>-->
+            <div>
+                <strong>Copyright</strong> OPNFV &copy; 2014-2015
+            </div>
+        </div>
+
+    </div>
+</div>
+
+<script>
+    $("document").scrollTop();
+</script>
\ No newline at end of file
diff --git a/utils/test/reporting/pages/bower.json b/utils/test/reporting/pages/bower.json
new file mode 100644 (file)
index 0000000..10ebce0
--- /dev/null
@@ -0,0 +1,59 @@
+{
+    "name": "opnfv",
+    "version": "0.0.0",
+    "dependencies": {
+        "angular": "^1.4.0",
+        "bootstrap": "^3.2.0",
+        "angular-animate": "^1.4.0",
+        "jquery-slimscroll": "slimscroll#^1.3.8",
+        "metisMenu": "~2.0.2",
+        "chosen": "^1.6.2",
+        "oclazyload": "^1.0.9",
+        "angular-bootstrap": "~1.1.2",
+        "angular-ui-router": "~0.2.15",
+        "angular-resource": "^1.6.0",
+        "angular-selectize2": "^3.0.1",
+        "components-font-awesome": "^4.7.0",
+        "angular-tooltips": "^1.1.8",
+        "jQuery-rwdImageMaps": "*",
+        "animate.css": "^3.5.2",
+        "ng-dialog": "^1.0.0",
+        "inspiniacss": "^0.0.1",
+        "angularUtils-pagination": "angular-utils-pagination#^0.11.1",
+        "angular-loading": "^0.1.4"
+    },
+    "resolutions": {
+        "angular": "~1.6.x"
+    },
+    "devDependencies": {
+        "angular-mocks": "^1.4.0"
+    },
+    "appPath": "app",
+    "moduleName": "opnfvApp",
+    "overrides": {
+        "bootstrap": {
+            "main": [
+                "less/bootstrap.less",
+                "dist/css/bootstrap.css",
+                "dist/js/bootstrap.js"
+            ]
+        },
+        "jQuery-rwdImageMaps": {
+            "main": [
+                "jquery.rwdImageMaps.min.js"
+            ]
+        },
+        "inspiniacss": {
+            "main": [
+                "style.css"
+            ]
+        },
+        "angular-loading": {
+            "main": [
+                "angular-loading.css",
+                "angular-loading.js",
+                "../spin.js/spin.js"
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/utils/test/reporting/pages/package.json b/utils/test/reporting/pages/package.json
new file mode 100644 (file)
index 0000000..82cdaf0
--- /dev/null
@@ -0,0 +1,35 @@
+{
+  "name": "opnfv",
+  "private": true,
+  "devDependencies": {
+    "autoprefixer-core": "^5.2.1",
+    "grunt": "^0.4.5",
+    "grunt-angular-templates": "^0.5.7",
+    "grunt-concurrent": "^1.0.0",
+    "grunt-contrib-clean": "^0.6.0",
+    "grunt-contrib-concat": "^0.5.0",
+    "grunt-contrib-connect": "^0.9.0",
+    "grunt-contrib-copy": "^0.7.0",
+    "grunt-contrib-cssmin": "^0.12.0",
+    "grunt-contrib-htmlmin": "^0.4.0",
+    "grunt-contrib-imagemin": "^1.0.0",
+    "grunt-contrib-jshint": "^0.11.0",
+    "grunt-contrib-uglify": "^0.7.0",
+    "grunt-contrib-watch": "^0.6.1",
+    "grunt-filerev": "^2.1.2",
+    "grunt-google-cdn": "^0.4.3",
+    "grunt-jscs": "^1.8.0",
+    "grunt-newer": "^1.1.0",
+    "grunt-ng-annotate": "^0.9.2",
+    "grunt-postcss": "^0.5.5",
+    "grunt-svgmin": "^2.0.0",
+    "grunt-usemin": "^3.0.0",
+    "grunt-wiredep": "^2.0.0",
+    "jit-grunt": "^0.9.1",
+    "time-grunt": "^1.0.0",
+    "jshint-stylish": "^1.0.0"
+  },
+  "engines": {
+    "node": ">=0.10.0"
+  }
+}
diff --git a/utils/test/reporting/pages/test/.jshintrc b/utils/test/reporting/pages/test/.jshintrc
new file mode 100644 (file)
index 0000000..b2ce4ef
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bitwise": true,
+  "browser": true,
+  "curly": true,
+  "eqeqeq": true,
+  "esnext": true,
+  "jasmine": true,
+  "latedef": true,
+  "noarg": true,
+  "node": true,
+  "strict": true,
+  "undef": true,
+  "unused": true,
+  "globals": {
+    "angular": false,
+    "inject": false
+  }
+}
diff --git a/utils/test/reporting/pages/test/karma.conf.js b/utils/test/reporting/pages/test/karma.conf.js
new file mode 100644 (file)
index 0000000..5c2e79b
--- /dev/null
@@ -0,0 +1,89 @@
+// Karma configuration
+// Generated on 2016-12-19
+
+module.exports = function(config) {
+  'use strict';
+
+  config.set({
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+    // base path, that will be used to resolve files and exclude
+    basePath: '../',
+
+    // testing framework to use (jasmine/mocha/qunit/...)
+    // as well as any additional frameworks (requirejs/chai/sinon/...)
+    frameworks: [
+      'jasmine'
+    ],
+
+    // list of files / patterns to load in the browser
+    files: [
+      // bower:js
+      'bower_components/jquery/dist/jquery.js',
+      'bower_components/angular/angular.js',
+      'bower_components/bootstrap/dist/js/bootstrap.js',
+      'bower_components/angular-animate/angular-animate.js',
+      'bower_components/jquery-slimscroll/jquery.slimscroll.js',
+      'bower_components/jquery-slimscroll/jquery.slimscroll.min.js',
+      'bower_components/metisMenu/dist/metisMenu.js',
+      'bower_components/chosen/chosen.jquery.js',
+      'bower_components/oclazyload/dist/ocLazyLoad.js',
+      'bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
+      'bower_components/angular-ui-router/release/angular-ui-router.js',
+      'bower_components/angular-resource/angular-resource.js',
+      'bower_components/sifter/sifter.js',
+      'bower_components/microplugin/src/microplugin.js',
+      'bower_components/selectize/dist/js/selectize.js',
+      'bower_components/angular-selectize2/dist/angular-selectize.js',
+      'bower_components/angular-tooltips/dist/angular-tooltips.min.js',
+      'bower_components/angular-mocks/angular-mocks.js',
+      // endbower
+      'app/scripts/**/*.js',
+      'test/mock/**/*.js',
+      'test/spec/**/*.js'
+    ],
+
+    // list of files / patterns to exclude
+    exclude: [
+    ],
+
+    // web server port
+    port: 8080,
+
+    // Start these browsers, currently available:
+    // - Chrome
+    // - ChromeCanary
+    // - Firefox
+    // - Opera
+    // - Safari (only Mac)
+    // - PhantomJS
+    // - IE (only Windows)
+    browsers: [
+      'PhantomJS'
+    ],
+
+    // Which plugins to enable
+    plugins: [
+      'karma-phantomjs-launcher',
+      'karma-jasmine'
+    ],
+
+    // Continuous Integration mode
+    // if true, it capture browsers, run tests and exit
+    singleRun: false,
+
+    colors: true,
+
+    // level of logging
+    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // Uncomment the following lines if you are using grunt's server to run the tests
+    // proxies: {
+    //   '/': 'http://localhost:9000/'
+    // },
+    // URL root prevent conflicts with the site root
+    // urlRoot: '_karma_'
+  });
+};
diff --git a/utils/test/reporting/pages/test/spec/controllers/main.js b/utils/test/reporting/pages/test/spec/controllers/main.js
new file mode 100644 (file)
index 0000000..b92366d
--- /dev/null
@@ -0,0 +1,23 @@
+'use strict';
+
+describe('Controller: MainCtrl', function () {
+
+  // load the controller's module
+  beforeEach(module('opnfvApp'));
+
+  var MainCtrl,
+    scope;
+
+  // Initialize the controller and a mock scope
+  beforeEach(inject(function ($controller, $rootScope) {
+    scope = $rootScope.$new();
+    MainCtrl = $controller('MainCtrl', {
+      $scope: scope
+      // place here mocked dependencies
+    });
+  }));
+
+  it('should attach a list of awesomeThings to the scope', function () {
+    expect(MainCtrl.awesomeThings.length).toBe(3);
+  });
+});
diff --git a/utils/test/reporting/pages/test/spec/directives/mydirective.js b/utils/test/reporting/pages/test/spec/directives/mydirective.js
new file mode 100644 (file)
index 0000000..598c9de
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+describe('Directive: myDirective', function () {
+
+  // load the directive's module
+  beforeEach(module('opnfvApp'));
+
+  var element,
+    scope;
+
+  beforeEach(inject(function ($rootScope) {
+    scope = $rootScope.$new();
+  }));
+
+  it('should make hidden element visible', inject(function ($compile) {
+    element = angular.element('<my-directive></my-directive>');
+    element = $compile(element)(scope);
+    expect(element.text()).toBe('this is the myDirective directive');
+  }));
+});
diff --git a/utils/test/reporting/reporting.yaml b/utils/test/reporting/reporting.yaml
new file mode 100644 (file)
index 0000000..8c5ce13
--- /dev/null
@@ -0,0 +1,69 @@
+---
+general:
+    installers:
+        - apex
+        - compass
+        - daisy
+        - fuel
+        - joid
+
+    versions:
+        - master
+        - danube
+
+    log:
+        log_file: reporting.log
+        log_level: ERROR
+
+    period: 10
+
+    nb_iteration_tests_success_criteria: 4
+
+    directories:
+        # Relative to the path where the repo is cloned:
+        dir_reporting: utils/tests/reporting/
+        dir_log: utils/tests/reporting/log/
+        dir_conf: utils/tests/reporting/conf/
+        dir_utils: utils/tests/reporting/utils/
+        dir_templates: utils/tests/reporting/templates/
+        dir_display: utils/tests/reporting/display/
+
+    url: testresults.opnfv.org/reporting/
+
+testapi:
+    url: testresults.opnfv.org/test/api/v1/results
+
+functest:
+    blacklist:
+        - ovno
+        - security_scan
+        - rally_sanity
+        - healthcheck
+        - odl_netvirt
+        - aaa
+        - cloudify_ims
+        - orchestra_ims
+        - juju_epc
+        - orchestra
+        - promise
+    max_scenario_criteria: 50
+    test_conf: https://git.opnfv.org/cgit/functest/plain/functest/ci/testcases.yaml
+    log_level: ERROR
+    jenkins_url: https://build.opnfv.org/ci/view/functest/job/
+    exclude_noha: "False"
+    exclude_virtual: "False"
+
+yardstick:
+    test_conf: https://git.opnfv.org/cgit/yardstick/plain/tests/ci/report_config.yaml
+    log_level: ERROR
+
+storperf:
+    test_list:
+        - snia_steady_state
+    log_level: ERROR
+
+qtip:
+
+bottleneck:
+
+vsperf:
diff --git a/utils/test/reporting/run_unit_tests.sh b/utils/test/reporting/run_unit_tests.sh
new file mode 100755 (executable)
index 0000000..6b0e3b2
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/bash
+set -o errexit
+set -o pipefail
+
+# ******************************
+# prepare the env for the tests
+# ******************************
+# Either Workspace is set (CI)
+if [ -z $WORKSPACE ]
+then
+    WORKSPACE="."
+fi
+
+export CONFIG_REPORTING_YAML=./reporting.yaml
+
+# ***************
+# Run unit tests
+# ***************
+echo "Running unit tests..."
+
+# start vitual env
+virtualenv $WORKSPACE/reporting_venv
+source $WORKSPACE/reporting_venv/bin/activate
+
+# install python packages
+easy_install -U setuptools
+easy_install -U pip
+pip install -r $WORKSPACE/docker/requirements.pip
+pip install -e $WORKSPACE
+
+python $WORKSPACE/setup.py develop
+
+# unit tests
+# TODO: remove cover-erase
+# To be deleted when all functest packages will be listed
+nosetests --with-xunit \
+         --cover-package=utils \
+         --with-coverage \
+         --cover-xml \
+         tests/unit
+rc=$?
+
+deactivate
diff --git a/utils/test/reporting/setup.py b/utils/test/reporting/setup.py
new file mode 100644 (file)
index 0000000..627785e
--- /dev/null
@@ -0,0 +1,22 @@
+##############################################################################
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+from setuptools import setup, find_packages
+
+
+setup(
+    name="reporting",
+    version="master",
+    packages=find_packages(),
+    include_package_data=True,
+    package_data={
+    },
+    url="https://www.opnfv.org",
+    install_requires=["coverage==4.1",
+                      "mock==1.3.0",
+                      "nose==1.3.7"],
+)
diff --git a/utils/test/reporting/storperf/reporting-status.py b/utils/test/reporting/storperf/reporting-status.py
new file mode 100644 (file)
index 0000000..888e339
--- /dev/null
@@ -0,0 +1,145 @@
+#!/usr/bin/python
+#
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+import datetime
+import jinja2
+import os
+
+# manage conf
+import utils.reporting_utils as rp_utils
+
+import utils.scenarioResult as sr
+
+installers = rp_utils.get_config('general.installers')
+versions = rp_utils.get_config('general.versions')
+PERIOD = rp_utils.get_config('general.period')
+
+# Logger
+logger = rp_utils.getLogger("Storperf-Status")
+reportingDate = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
+
+logger.info("*******************************************")
+logger.info("*   Generating reporting scenario status  *")
+logger.info("*   Data retention = %s days              *" % PERIOD)
+logger.info("*                                         *")
+logger.info("*******************************************")
+
+# retrieve the list of storperf tests
+storperf_tests = rp_utils.get_config('storperf.test_list')
+logger.info("Storperf tests: %s" % storperf_tests)
+
+# For all the versions
+for version in versions:
+    # For all the installers
+    for installer in installers:
+        # get scenarios results data
+        # for the moment we consider only 1 case snia_steady_state
+        scenario_results = rp_utils.getScenarios("snia_steady_state",
+                                                 installer,
+                                                 version)
+        # logger.info("scenario_results: %s" % scenario_results)
+
+        scenario_stats = rp_utils.getScenarioStats(scenario_results)
+        logger.info("scenario_stats: %s" % scenario_stats)
+        items = {}
+        scenario_result_criteria = {}
+
+        # From each scenarios get results list
+        for s, s_result in scenario_results.items():
+            logger.info("---------------------------------")
+            logger.info("installer %s, version %s, scenario %s", installer,
+                        version, s)
+            ten_criteria = len(s_result)
+
+            ten_score = 0
+            for v in s_result:
+                if "PASS" in v['criteria']:
+                    ten_score += 1
+
+            logger.info("ten_score: %s / %s" % (ten_score, ten_criteria))
+
+            four_score = 0
+            try:
+                LASTEST_TESTS = rp_utils.get_config(
+                    'general.nb_iteration_tests_success_criteria')
+                s_result.sort(key=lambda x: x['start_date'])
+                four_result = s_result[-LASTEST_TESTS:]
+                logger.debug("four_result: {}".format(four_result))
+                logger.debug("LASTEST_TESTS: {}".format(LASTEST_TESTS))
+                # logger.debug("four_result: {}".format(four_result))
+                four_criteria = len(four_result)
+                for v in four_result:
+                    if "PASS" in v['criteria']:
+                        four_score += 1
+                logger.info("4 Score: %s / %s " % (four_score,
+                                                   four_criteria))
+            except:
+                logger.error("Impossible to retrieve the four_score")
+
+            try:
+                s_status = (four_score * 100) / four_criteria
+            except:
+                s_status = 0
+            logger.info("Score percent = %s" % str(s_status))
+            s_four_score = str(four_score) + '/' + str(four_criteria)
+            s_ten_score = str(ten_score) + '/' + str(ten_criteria)
+            s_score_percent = str(s_status)
+
+            logger.debug(" s_status: {}".format(s_status))
+            if s_status == 100:
+                logger.info(">>>>> scenario OK, save the information")
+            else:
+                logger.info(">>>> scenario not OK, last 4 iterations = %s, \
+                             last 10 days = %s" % (s_four_score, s_ten_score))
+
+            s_url = ""
+            if len(s_result) > 0:
+                build_tag = s_result[len(s_result)-1]['build_tag']
+                logger.debug("Build tag: %s" % build_tag)
+                s_url = s_url = rp_utils.getJenkinsUrl(build_tag)
+                logger.info("last jenkins url: %s" % s_url)
+
+            # Save daily results in a file
+            path_validation_file = ("./display/" + version +
+                                    "/storperf/scenario_history.txt")
+
+            if not os.path.exists(path_validation_file):
+                with open(path_validation_file, 'w') as f:
+                    info = 'date,scenario,installer,details,score\n'
+                    f.write(info)
+
+            with open(path_validation_file, "a") as f:
+                info = (reportingDate + "," + s + "," + installer +
+                        "," + s_ten_score + "," +
+                        str(s_score_percent) + "\n")
+                f.write(info)
+
+            scenario_result_criteria[s] = sr.ScenarioResult(s_status,
+                                                            s_four_score,
+                                                            s_ten_score,
+                                                            s_score_percent,
+                                                            s_url)
+
+            logger.info("--------------------------")
+
+        templateLoader = jinja2.FileSystemLoader(".")
+        templateEnv = jinja2.Environment(loader=templateLoader,
+                                         autoescape=True)
+
+        TEMPLATE_FILE = "./storperf/template/index-status-tmpl.html"
+        template = templateEnv.get_template(TEMPLATE_FILE)
+
+        outputText = template.render(scenario_results=scenario_result_criteria,
+                                     installer=installer,
+                                     period=PERIOD,
+                                     version=version,
+                                     date=reportingDate)
+
+        with open("./display/" + version +
+                  "/storperf/status-" + installer + ".html", "wb") as fh:
+            fh.write(outputText)
diff --git a/utils/test/reporting/storperf/template/index-status-tmpl.html b/utils/test/reporting/storperf/template/index-status-tmpl.html
new file mode 100644 (file)
index 0000000..e0fcc68
--- /dev/null
@@ -0,0 +1,111 @@
+ <html>
+  <head>
+    <meta charset="utf-8">
+    <!-- Bootstrap core CSS -->
+    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
+    <link href="../../css/default.css" rel="stylesheet">
+    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
+    <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
+    <script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
+    <script type="text/javascript" src="../../js/gauge.js"></script>
+    <script type="text/javascript" src="../../js/trend.js"></script>
+    <script>
+        function onDocumentReady() {
+            // Gauge management
+            {% for scenario in scenario_results.keys() -%}
+            var gaugeScenario{{loop.index}} = gauge('#gaugeScenario{{loop.index}}');
+            {%- endfor %}
+            // assign success rate to the gauge
+            function updateReadings() {
+                {% for scenario in scenario_results.keys() -%}
+                 gaugeScenario{{loop.index}}.update({{scenario_results[scenario].getScorePercent()}});
+                 {%- endfor %}
+            }
+            updateReadings();
+        }
+
+        // trend line management
+        d3.csv("./scenario_history.txt", function(data) {
+            // ***************************************
+            // Create the trend line
+            {% for scenario in scenario_results.keys() -%}
+            // for scenario {{scenario}}
+            // Filter results
+                var trend{{loop.index}} = data.filter(function(row) {
+                    return row["scenario"]=="{{scenario}}" && row["installer"]=="{{installer}}";
+                })
+            // Parse the date
+            trend{{loop.index}}.forEach(function(d) {
+                d.date = parseDate(d.date);
+                d.score = +d.score
+            });
+            // Draw the trend line
+            var mytrend = trend("#trend_svg{{loop.index}}",trend{{loop.index}})
+            // ****************************************
+            {%- endfor %}
+        });
+        if ( !window.isLoaded ) {
+            window.addEventListener("load", function() {
+            onDocumentReady();
+            }, false);
+        } else {
+            onDocumentReady();
+        }
+    </script>
+    <script type="text/javascript">
+    $(document).ready(function (){
+        $(".btn-more").click(function() {
+            $(this).hide();
+            $(this).parent().find(".panel-default").show();
+        });
+    })
+    </script>
+  </head>
+    <body>
+    <div class="container">
+      <div class="masthead">
+          <h3 class="text-muted">Storperf status page ({{version}}, {{date}})</h3>
+        <nav>
+          <ul class="nav nav-justified">
+            <li class="active"><a href="http://testresults.opnfv.org/reporting/index.html">Home</a></li>
+            <li><a href="status-apex.html">Apex</a></li>
+            <li><a href="status-compass.html">Compass</a></li>
+            <li><a href="status-daisy.html">Daisy</a></li>
+            <li><a href="status-fuel.html">Fuel</a></li>
+            <li><a href="status-joid.html">Joid</a></li>
+          </ul>
+        </nav>
+      </div>
+<div class="row">
+    <div class="col-md-1"></div>
+    <div class="col-md-10">
+        <div class="page-header">
+            <h2>{{installer}}</h2>
+        </div>
+
+        <div class="scenario-overview">
+            <div class="panel-heading"><h4><b>List of last scenarios ({{version}}) run over the last {{period}} days </b></h4></div>
+                <table class="table">
+                    <tr>
+                        <th width="40%">Scenario</th>
+                        <th width="20%">Status</th>
+                        <th width="20%">Trend</th>
+                        <th width="10%">Last 4 Iterations</th>
+                        <th width="10%">Last 10 Days</th>
+                    </tr>
+                        {% for scenario,result in scenario_results.iteritems() -%}
+                            <tr class="tr-ok">
+                                <td><a href="{{scenario_results[scenario].getLastUrl()}}">{{scenario}}</a></td>
+                                <td><div id="gaugeScenario{{loop.index}}"></div></td>
+                                <td><div id="trend_svg{{loop.index}}"></div></td>
+                                <td>{{scenario_results[scenario].getFourDaysScore()}}</td>
+                                <td>{{scenario_results[scenario].getTenDaysScore()}}</td>
+                            </tr>
+                        {%- endfor %}
+                </table>
+        </div>
+
+
+    </div>
+    <div class="col-md-1"></div>
+</div>
diff --git a/utils/test/reporting/tests/__init__.py b/utils/test/reporting/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/reporting/tests/unit/__init__.py b/utils/test/reporting/tests/unit/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/reporting/tests/unit/utils/__init__.py b/utils/test/reporting/tests/unit/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/reporting/tests/unit/utils/test_utils.py b/utils/test/reporting/tests/unit/utils/test_utils.py
new file mode 100644 (file)
index 0000000..b9c3980
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import logging
+import unittest
+
+from utils import reporting_utils
+
+
+class reportingUtilsTesting(unittest.TestCase):
+
+    logging.disable(logging.CRITICAL)
+
+    def setUp(self):
+        self.test = reporting_utils
+
+    def test_getConfig(self):
+        self.assertEqual(self.test.get_config("general.period"), 10)
+# TODO
+# ...
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/utils/test/reporting/utils/__init__.py b/utils/test/reporting/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/reporting/utils/reporting_utils.py b/utils/test/reporting/utils/reporting_utils.py
new file mode 100644 (file)
index 0000000..aab7a3f
--- /dev/null
@@ -0,0 +1,376 @@
+#!/usr/bin/python
+#
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+from urllib2 import Request, urlopen, URLError
+import logging
+import json
+import os
+import pdfkit
+import yaml
+
+
+# ----------------------------------------------------------
+#
+#               YAML UTILS
+#
+# -----------------------------------------------------------
+def get_parameter_from_yaml(parameter, file):
+    """
+    Returns the value of a given parameter in file.yaml
+    parameter must be given in string format with dots
+    Example: general.openstack.image_name
+    """
+    with open(file) as f:
+        file_yaml = yaml.safe_load(f)
+    f.close()
+    value = file_yaml
+    for element in parameter.split("."):
+        value = value.get(element)
+        if value is None:
+            raise ValueError("The parameter %s is not defined in"
+                             " reporting.yaml" % parameter)
+    return value
+
+
+def get_config(parameter):
+    yaml_ = os.environ["CONFIG_REPORTING_YAML"]
+    return get_parameter_from_yaml(parameter, yaml_)
+
+
+# ----------------------------------------------------------
+#
+#               LOGGER UTILS
+#
+# -----------------------------------------------------------
+def getLogger(module):
+    logFormatter = logging.Formatter("%(asctime)s [" +
+                                     module +
+                                     "] [%(levelname)-5.5s]  %(message)s")
+    logger = logging.getLogger()
+    log_file = get_config('general.log.log_file')
+    log_level = get_config('general.log.log_level')
+
+    fileHandler = logging.FileHandler("{0}/{1}".format('.', log_file))
+    fileHandler.setFormatter(logFormatter)
+    logger.addHandler(fileHandler)
+
+    consoleHandler = logging.StreamHandler()
+    consoleHandler.setFormatter(logFormatter)
+    logger.addHandler(consoleHandler)
+    logger.setLevel(log_level)
+    return logger
+
+
+# ----------------------------------------------------------
+#
+#               REPORTING UTILS
+#
+# -----------------------------------------------------------
+def getApiResults(case, installer, scenario, version):
+    results = json.dumps([])
+    # to remove proxy (to be removed at the end for local test only)
+    # proxy_handler = urllib2.ProxyHandler({})
+    # opener = urllib2.build_opener(proxy_handler)
+    # urllib2.install_opener(opener)
+    # url = "http://127.0.0.1:8000/results?case=" + case + \
+    #       "&period=30&installer=" + installer
+    period = get_config('general.period')
+    url_base = get_config('testapi.url')
+    nb_tests = get_config('general.nb_iteration_tests_success_criteria')
+
+    url = ("http://" + url_base + "?case=" + case +
+           "&period=" + str(period) + "&installer=" + installer +
+           "&scenario=" + scenario + "&version=" + version +
+           "&last=" + str(nb_tests))
+    request = Request(url)
+
+    try:
+        response = urlopen(request)
+        k = response.read()
+        results = json.loads(k)
+    except URLError as e:
+        print('No kittez. Got an error code:', e)
+
+    return results
+
+
+def getScenarios(case, installer, version):
+
+    try:
+        case = case.getName()
+    except:
+        # if case is not an object test case, try the string
+        if type(case) == str:
+            case = case
+        else:
+            raise ValueError("Case cannot be evaluated")
+
+    period = get_config('general.period')
+    url_base = get_config('testapi.url')
+
+    url = ("http://" + url_base + "?case=" + case +
+           "&period=" + str(period) + "&installer=" + installer +
+           "&version=" + version)
+    request = Request(url)
+
+    try:
+        response = urlopen(request)
+        k = response.read()
+        results = json.loads(k)
+        test_results = results['results']
+    except URLError as e:
+        print('Got an error code:', e)
+
+    if test_results is not None:
+        test_results.reverse()
+
+        scenario_results = {}
+
+        for r in test_results:
+            # Retrieve all the scenarios per installer
+            if not r['scenario'] in scenario_results.keys():
+                scenario_results[r['scenario']] = []
+            # Do we consider results from virtual pods ...
+            # Do we consider results for non HA scenarios...
+            exclude_virtual_pod = get_config('functest.exclude_virtual')
+            exclude_noha = get_config('functest.exclude_noha')
+            if ((exclude_virtual_pod and "virtual" in r['pod_name']) or
+                    (exclude_noha and "noha" in r['scenario'])):
+                print("exclude virtual pod results...")
+            else:
+                scenario_results[r['scenario']].append(r)
+
+    return scenario_results
+
+
+def getScenarioStats(scenario_results):
+    scenario_stats = {}
+    for k, v in scenario_results.iteritems():
+        scenario_stats[k] = len(v)
+
+    return scenario_stats
+
+
+# TODO convergence with above function getScenarios
+def getScenarioStatus(installer, version):
+    period = get_config('general.period')
+    url_base = get_config('testapi.url')
+
+    url = ("http://" + url_base + "?case=scenario_status" +
+           "&installer=" + installer +
+           "&version=" + version + "&period=" + str(period))
+    request = Request(url)
+
+    try:
+        response = urlopen(request)
+        k = response.read()
+        response.close()
+        results = json.loads(k)
+        test_results = results['results']
+    except URLError as e:
+        print('Got an error code:', e)
+
+    scenario_results = {}
+    result_dict = {}
+    if test_results is not None:
+        for r in test_results:
+            if r['stop_date'] != 'None' and r['criteria'] is not None:
+                if not r['scenario'] in scenario_results.keys():
+                    scenario_results[r['scenario']] = []
+                scenario_results[r['scenario']].append(r)
+
+        for k, v in scenario_results.items():
+            # scenario_results[k] = v[:LASTEST_TESTS]
+            s_list = []
+            for element in v:
+                if element['criteria'] == 'SUCCESS':
+                    s_list.append(1)
+                else:
+                    s_list.append(0)
+            result_dict[k] = s_list
+
+    # return scenario_results
+    return result_dict
+
+
+def getNbtestOk(results):
+    nb_test_ok = 0
+    for r in results:
+        for k, v in r.iteritems():
+            try:
+                if "PASS" in v:
+                    nb_test_ok += 1
+            except:
+                print("Cannot retrieve test status")
+    return nb_test_ok
+
+
+def getResult(testCase, installer, scenario, version):
+
+    # retrieve raw results
+    results = getApiResults(testCase, installer, scenario, version)
+    # let's concentrate on test results only
+    test_results = results['results']
+
+    # if results found, analyze them
+    if test_results is not None:
+        test_results.reverse()
+
+        scenario_results = []
+
+        # print " ---------------- "
+        # print test_results
+        # print " ---------------- "
+        # print "nb of results:" + str(len(test_results))
+
+        for r in test_results:
+            # print r["start_date"]
+            # print r["criteria"]
+            scenario_results.append({r["start_date"]: r["criteria"]})
+        # sort results
+        scenario_results.sort()
+        # 4 levels for the results
+        # 3: 4+ consecutive runs passing the success criteria
+        # 2: <4 successful consecutive runs but passing the criteria
+        # 1: close to pass the success criteria
+        # 0: 0% success, not passing
+        # -1: no run available
+        test_result_indicator = 0
+        nbTestOk = getNbtestOk(scenario_results)
+
+        # print "Nb test OK (last 10 days):"+ str(nbTestOk)
+        # check that we have at least 4 runs
+        if len(scenario_results) < 1:
+            # No results available
+            test_result_indicator = -1
+        elif nbTestOk < 1:
+            test_result_indicator = 0
+        elif nbTestOk < 2:
+            test_result_indicator = 1
+        else:
+            # Test the last 4 run
+            if (len(scenario_results) > 3):
+                last4runResults = scenario_results[-4:]
+                nbTestOkLast4 = getNbtestOk(last4runResults)
+                # print "Nb test OK (last 4 run):"+ str(nbTestOkLast4)
+                if nbTestOkLast4 > 3:
+                    test_result_indicator = 3
+                else:
+                    test_result_indicator = 2
+            else:
+                test_result_indicator = 2
+    return test_result_indicator
+
+
+def getJenkinsUrl(build_tag):
+    # e.g. jenkins-functest-apex-apex-daily-colorado-daily-colorado-246
+    # id = 246
+    # jenkins-functest-compass-huawei-pod5-daily-master-136
+    # id = 136
+    # note it is linked to jenkins format
+    # if this format changes...function to be adapted....
+    url_base = get_config('functest.jenkins_url')
+    try:
+        build_id = [int(s) for s in build_tag.split("-") if s.isdigit()]
+        url_id = (build_tag[8:-(len(str(build_id[0])) + 1)] +
+                  "/" + str(build_id[0]))
+        jenkins_url = url_base + url_id + "/console"
+    except:
+        print('Impossible to get jenkins url:')
+
+    if "jenkins-" not in build_tag:
+        jenkins_url = None
+
+    return jenkins_url
+
+
+def getScenarioPercent(scenario_score, scenario_criteria):
+    score = 0.0
+    try:
+        score = float(scenario_score) / float(scenario_criteria) * 100
+    except:
+        print('Impossible to calculate the percentage score')
+    return score
+
+
+# *********
+# Yardstick
+# *********
+def subfind(given_list, pattern_list):
+    LASTEST_TESTS = get_config('general.nb_iteration_tests_success_criteria')
+    for i in range(len(given_list)):
+        if given_list[i] == pattern_list[0] and \
+                given_list[i:i + LASTEST_TESTS] == pattern_list:
+            return True
+    return False
+
+
+def _get_percent(status):
+
+    if status * 100 % 6:
+        return round(float(status) * 100 / 6, 1)
+    else:
+        return status * 100 / 6
+
+
+def get_percent(four_list, ten_list):
+    four_score = 0
+    ten_score = 0
+
+    for v in four_list:
+        four_score += v
+    for v in ten_list:
+        ten_score += v
+
+    LASTEST_TESTS = get_config('general.nb_iteration_tests_success_criteria')
+    if four_score == LASTEST_TESTS:
+        status = 6
+    elif subfind(ten_list, [1, 1, 1, 1]):
+        status = 5
+    elif ten_score == 0:
+        status = 0
+    else:
+        status = four_score + 1
+
+    return _get_percent(status)
+
+
+def _test():
+    status = getScenarioStatus("compass", "master")
+    print("status:++++++++++++++++++++++++")
+    print(json.dumps(status, indent=4))
+
+
+# ----------------------------------------------------------
+#
+#               Export
+#
+# -----------------------------------------------------------
+
+def export_csv(scenario_file_name, installer, version):
+    # csv
+    # generate sub files based on scenario_history.txt
+    scenario_installer_file_name = ("./display/" + version +
+                                    "/functest/scenario_history_" +
+                                    installer + ".csv")
+    scenario_installer_file = open(scenario_installer_file_name, "a")
+    with open(scenario_file_name, "r") as f:
+        scenario_installer_file.write("date,scenario,installer,detail,score\n")
+        for line in f:
+            if installer in line:
+                scenario_installer_file.write(line)
+        scenario_installer_file.close
+
+
+def export_pdf(pdf_path, pdf_doc_name):
+    try:
+        pdfkit.from_file(pdf_path, pdf_doc_name)
+    except IOError:
+        print("Error but pdf generated anyway...")
+    except:
+        print("impossible to generate PDF")
 
 class ScenarioResult(object):
     def __init__(self, status, four_days_score='', ten_days_score='',
-                 score_percent=0.0):
+                 score_percent=0.0, last_url=''):
         self.status = status
         self.four_days_score = four_days_score
         self.ten_days_score = ten_days_score
         self.score_percent = score_percent
+        self.last_url = last_url
 
     def getStatus(self):
         return self.status
@@ -27,3 +28,6 @@ class ScenarioResult(object):
 
     def getScorePercent(self):
         return self.score_percent
+
+    def getLastUrl(self):
+        return self.last_url
index 49809e9..12f42ca 100644 (file)
@@ -10,31 +10,36 @@ import datetime
 import jinja2
 import os
 
-import reportingUtils as utils
-import reportingConf as conf
-import scenarioResult as sr
+import utils.scenarioResult as sr
 from scenarios import config as cf
 
+# manage conf
+import utils.reporting_utils as rp_utils
+
+installers = rp_utils.get_config('general.installers')
+versions = rp_utils.get_config('general.versions')
+PERIOD = rp_utils.get_config('general.period')
+
 # Logger
-logger = utils.getLogger("Yardstick-Status")
+logger = rp_utils.getLogger("Yardstick-Status")
 reportingDate = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
 
 logger.info("*******************************************")
 logger.info("*   Generating reporting scenario status  *")
-logger.info("*   Data retention = %s days              *" % conf.PERIOD)
+logger.info("*   Data retention = %s days              *" % PERIOD)
 logger.info("*                                         *")
 logger.info("*******************************************")
 
 
 # For all the versions
-for version in conf.versions:
+for version in versions:
     # For all the installers
-    for installer in conf.installers:
+    for installer in installers:
         # get scenarios results data
-        scenario_results = utils.getScenarioStatus(installer, version)
+        scenario_results = rp_utils.getScenarioStatus(installer, version)
         if 'colorado' == version:
-            stable_result = utils.getScenarioStatus(installer,
-                                                    'stable/colorado')
+            stable_result = rp_utils.getScenarioStatus(installer,
+                                                       'stable/colorado')
             for k, v in stable_result.items():
                 if k not in scenario_results.keys():
                     scenario_results[k] = []
@@ -48,24 +53,26 @@ for version in conf.versions:
         # From each scenarios get results list
         for s, s_result in scenario_results.items():
             logger.info("---------------------------------")
-            logger.info("installer %s, version %s, scenario %s:" % (installer,
-                                                                    version, s))
+            logger.info("installer %s, version %s, scenario %s", installer,
+                        version, s)
 
             ten_criteria = len(s_result)
             ten_score = 0
             for v in s_result:
                 ten_score += v
 
-            four_result = s_result[:conf.LASTEST_TESTS]
+            LASTEST_TESTS = rp_utils.get_config(
+                'general.nb_iteration_tests_success_criteria')
+            four_result = s_result[:LASTEST_TESTS]
             four_criteria = len(four_result)
             four_score = 0
             for v in four_result:
                 four_score += v
 
-            s_status = str(utils.get_percent(four_result, s_result))
+            s_status = str(rp_utils.get_percent(four_result, s_result))
             s_four_score = str(four_score) + '/' + str(four_criteria)
             s_ten_score = str(ten_score) + '/' + str(ten_criteria)
-            s_score_percent = utils.get_percent(four_result, s_result)
+            s_score_percent = rp_utils.get_percent(four_result, s_result)
 
             if '100' == s_status:
                 logger.info(">>>>> scenario OK, save the information")
@@ -74,9 +81,8 @@ for version in conf.versions:
                             last 10 days = %s" % (s_four_score, s_ten_score))
 
             # Save daily results in a file
-            path_validation_file = (conf.REPORTING_PATH +
-                                    "/release/" + version +
-                                    "/scenario_history.txt")
+            path_validation_file = ("./display/" + version +
+                                    "/yardstick/scenario_history.txt")
 
             if not os.path.exists(path_validation_file):
                 with open(path_validation_file, 'w') as f:
@@ -96,18 +102,19 @@ for version in conf.versions:
 
             logger.info("--------------------------")
 
-        templateLoader = jinja2.FileSystemLoader(conf.REPORTING_PATH)
-        templateEnv = jinja2.Environment(loader=templateLoader, autoescape=True)
+        templateLoader = jinja2.FileSystemLoader(".")
+        templateEnv = jinja2.Environment(loader=templateLoader,
+                                         autoescape=True)
 
-        TEMPLATE_FILE = "/template/index-status-tmpl.html"
+        TEMPLATE_FILE = "./yardstick/template/index-status-tmpl.html"
         template = templateEnv.get_template(TEMPLATE_FILE)
 
         outputText = template.render(scenario_results=scenario_result_criteria,
                                      installer=installer,
-                                     period=conf.PERIOD,
+                                     period=PERIOD,
                                      version=version,
                                      date=reportingDate)
 
-        with open(conf.REPORTING_PATH + "/release/" + version +
-                  "/index-status-" + installer + ".html", "wb") as fh:
+        with open("./display/" + version +
+                  "/yardstick/status-" + installer + ".html", "wb") as fh:
             fh.write(outputText)
diff --git a/utils/test/reporting/yardstick/reportingConf.py b/utils/test/reporting/yardstick/reportingConf.py
deleted file mode 100644 (file)
index 2db41f0..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/python
-#
-# This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Reporting: Declaration of the variables
-#
-# ****************************************************
-installers = ["apex", "compass", "fuel", "joid"]
-
-versions = ["master", "colorado"]
-
-# get data in the past 10 days
-PERIOD = 10
-
-# get the lastest 4 test results to determinate the success criteria
-LASTEST_TESTS = 4
-
-REPORTING_PATH = "."
-
-URL_BASE = 'http://testresults.opnfv.org/test/api/v1/results'
-TEST_CONF = "https://git.opnfv.org/cgit/yardstick/plain/tests/ci/report_config.yaml"
-
-# LOG_LEVEL = "ERROR"
-LOG_LEVEL = "INFO"
-LOG_FILE = REPORTING_PATH + "/reporting.log"
diff --git a/utils/test/reporting/yardstick/reportingUtils.py b/utils/test/reporting/yardstick/reportingUtils.py
deleted file mode 100644 (file)
index ec9ed76..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/usr/bin/python
-#
-# This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-from urllib2 import Request, urlopen, URLError
-import logging
-import json
-import reportingConf as conf
-
-
-def getLogger(module):
-    logFormatter = logging.Formatter("%(asctime)s [" +
-                                     module +
-                                     "] [%(levelname)-5.5s]  %(message)s")
-    logger = logging.getLogger()
-
-    fileHandler = logging.FileHandler("{0}/{1}".format('.', conf.LOG_FILE))
-    fileHandler.setFormatter(logFormatter)
-    logger.addHandler(fileHandler)
-
-    consoleHandler = logging.StreamHandler()
-    consoleHandler.setFormatter(logFormatter)
-    logger.addHandler(consoleHandler)
-    logger.setLevel(conf.LOG_LEVEL)
-    return logger
-
-
-def getScenarioStatus(installer, version):
-    url = (conf.URL_BASE + "?case=" + "scenario_status" +
-           "&installer=" + installer +
-           "&version=" + version + "&period=" + str(conf.PERIOD))
-    request = Request(url)
-
-    try:
-        response = urlopen(request)
-        k = response.read()
-        response.close()
-        results = json.loads(k)
-        test_results = results['results']
-    except URLError, e:
-        print 'Got an error code:', e
-
-    scenario_results = {}
-    result_dict = {}
-    if test_results is not None:
-        for r in test_results:
-            if r['stop_date'] != 'None' and r['criteria'] is not None:
-                if not r['scenario'] in scenario_results.keys():
-                    scenario_results[r['scenario']] = []
-                scenario_results[r['scenario']].append(r)
-
-        for k, v in scenario_results.items():
-            # scenario_results[k] = v[:conf.LASTEST_TESTS]
-            s_list = []
-            for element in v:
-                if element['criteria'] == 'SUCCESS':
-                    s_list.append(1)
-                else:
-                    s_list.append(0)
-            result_dict[k] = s_list
-
-    # return scenario_results
-    return result_dict
-
-
-def subfind(given_list, pattern_list):
-
-    for i in range(len(given_list)):
-        if given_list[i] == pattern_list[0] and \
-                given_list[i:i + conf.LASTEST_TESTS] == pattern_list:
-            return True
-    return False
-
-
-def _get_percent(status):
-
-    if status * 100 % 6:
-        return round(float(status) * 100 / 6, 1)
-    else:
-        return status * 100 / 6
-
-
-def get_percent(four_list, ten_list):
-    four_score = 0
-    ten_score = 0
-
-    for v in four_list:
-        four_score += v
-    for v in ten_list:
-        ten_score += v
-
-    if four_score == conf.LASTEST_TESTS:
-        status = 6
-    elif subfind(ten_list, [1, 1, 1, 1]):
-        status = 5
-    elif ten_score == 0:
-        status = 0
-    else:
-        status = four_score + 1
-
-    return _get_percent(status)
-
-
-def _test():
-    status = getScenarioStatus("compass", "master")
-    print "status:++++++++++++++++++++++++"
-    print json.dumps(status, indent=4)
-
-
-if __name__ == '__main__':    # pragma: no cover
-    _test()
index 590fea2..26e8c8b 100644 (file)
@@ -1,11 +1,18 @@
-import yaml
-import os
+#!/usr/bin/python
+#
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
 import requests
+import yaml
 
-import reportingConf as conf
-
+import utils.reporting_utils as rp_utils
 
-response = requests.get(conf.TEST_CONF)
+yardstick_conf = rp_utils.get_config('yardstick.test_conf')
+response = requests.get(yardstick_conf)
 yaml_file = yaml.safe_load(response.text)
 reporting = yaml_file.get('reporting')
 
@@ -15,6 +22,6 @@ for element in reporting:
     name = element['name']
     scenarios = element['scenario']
     for s in scenarios:
-        if not config.has_key(name):
+        if name not in config:
             config[name] = {}
         config[name][s] = True
index 5a4dc34..b6f237a 100644 (file)
@@ -3,12 +3,12 @@
     <meta charset="utf-8">
     <!-- Bootstrap core CSS -->
     <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
-    <link href="../../../css/default.css" rel="stylesheet">
+    <link href="../../css/default.css" rel="stylesheet">
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
     <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
     <script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
-    <script type="text/javascript" src="../../../js/gauge.js"></script>
-    <script type="text/javascript" src="../../../js/trend.js"></script>
+    <script type="text/javascript" src="../../js/gauge.js"></script>
+    <script type="text/javascript" src="../../js/trend.js"></script>
     <script>
         function onDocumentReady() {
             // Gauge management
@@ -25,8 +25,7 @@
         }
 
         // trend line management
-        //d3.csv("./scenario_history.txt", function(data) {
-        d3.csv("./scenario_history.txt", function(data) {
+        d3.csv("./scenario_history.csv", function(data) {
             // ***************************************
             // Create the trend line
             {% for scenario in scenario_results.keys() -%}
diff --git a/utils/test/testapi/.coveragerc b/utils/test/testapi/.coveragerc
new file mode 100644 (file)
index 0000000..23fb97f
--- /dev/null
@@ -0,0 +1,27 @@
+# .coveragerc to control coverage.py
+
+[run]
+branch = True
+source =
+    opnfv_testapi
+
+[report]
+# Regexes for lines to exclude from consideration
+exclude_lines =
+       # Have to re-enable the standard pragma
+       pragma: no cover
+
+       # Don't complain about missing debug-only code:
+       def __repr__
+       if self\.debug
+
+       # Don't complain if tests don't hit defensive assertion code:
+       raise AssertionError
+       raise NotImplementedError
+
+       # Don't complain if non-runnable code isn't run:
+       if 0:
+       if __name__ == .__main__.:
+
+ignore_errors = True
+
diff --git a/utils/test/testapi/deployment/deploy.py b/utils/test/testapi/deployment/deploy.py
new file mode 100644 (file)
index 0000000..748bd34
--- /dev/null
@@ -0,0 +1,40 @@
+import argparse
+import os
+
+from jinja2 import Environment, FileSystemLoader
+
+env = Environment(loader=FileSystemLoader('./'))
+docker_compose_yml = './docker-compose.yml'
+docker_compose_template = './docker-compose.yml.template'
+
+
+def render_docker_compose(port, swagger_url):
+    vars = {
+        "expose_port": port,
+        "swagger_url": swagger_url,
+    }
+    template = env.get_template(docker_compose_template)
+    yml = template.render(vars=vars)
+
+    with open(docker_compose_yml, 'w') as f:
+        f.write(yml)
+        f.close()
+
+
+def main(args):
+    render_docker_compose(args.expose_port, args.swagger_url)
+    os.system('docker-compose -f {} up -d'.format(docker_compose_yml))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Backup MongoDBs')
+    parser.add_argument('-p', '--expose-port',
+                        type=int,
+                        required=False,
+                        default=8000,
+                        help='testapi exposed port')
+    parser.add_argument('-su', '--swagger-url',
+                        type=str,
+                        required=True,
+                        help='testapi exposed swagger-url')
+    main(parser.parse_args())
diff --git a/utils/test/testapi/deployment/docker-compose.yml.template b/utils/test/testapi/deployment/docker-compose.yml.template
new file mode 100644 (file)
index 0000000..5b131f7
--- /dev/null
@@ -0,0 +1,15 @@
+version: '2'
+services:
+  mongo:
+    image: mongo:3.2.1
+    container_name: opnfv-mongo
+  testapi:
+    image: opnfv/testapi:latest
+    container_name: opnfv-testapi
+    environment:
+      - mongodb_url=mongodb://mongo:27017/
+      - swagger_url={{ vars.swagger_url }}
+    ports:
+      - "{{ vars.expose_port }}:8000"
+    links:
+      - mongo
index b0272e6..e031e19 100644 (file)
@@ -8,13 +8,12 @@
 #    $ docker build -t opnfv/testapi:tag .
 #
 # Execution:
-#    $ docker run -dti -p 8000:8000 \
-#      -e "swagger_url=http://10.63.243.17:8000" \
+#    $ docker run -dti -p 8001:8000 \
+#      -e "swagger_url=http://10.63.243.17:8001" \
 #      -e "mongodb_url=mongodb://10.63.243.17:27017/" \
-#      -e "api_port=8000"
 #      opnfv/testapi:tag
 #
-# NOTE: providing swagger_url, api_port, mongodb_url is optional.
+# NOTE: providing swagger_url, mongodb_url is optional.
 #       If not provided, it will use the default one
 #       configured in config.ini
 #
@@ -48,5 +47,5 @@ RUN git clone https://gerrit.opnfv.org/gerrit/releng /home/releng
 
 WORKDIR /home/releng/utils/test/testapi/
 RUN pip install -r requirements.txt
-RUN python setup.py install
+RUN bash install.sh
 CMD ["bash", "docker/start-server.sh"]
index 99433cc..9f07efb 100755 (executable)
@@ -9,8 +9,3 @@ fi
 if [ "$swagger_url" != "" ]; then
     sudo crudini --set --existing $FILE swagger base_url $swagger_url
 fi
-
-if [ "$api_port" != "" ];then
-    sudo crudini --set --existing $FILE api port $api_port
-fi
-
index 0edb73a..77cc6c6 100644 (file)
@@ -11,6 +11,7 @@ dbname = test_results_collection
 port = 8000
 # With debug_on set to true, error traces will be shown in HTTP responses
 debug = True
+authenticate = False
 
 [swagger]
 base_url = http://localhost:8000
diff --git a/utils/test/testapi/htmlize/doc-build.sh b/utils/test/testapi/htmlize/doc-build.sh
new file mode 100644 (file)
index 0000000..33560ce
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -o errexit
+
+# Create virtual environment
+virtualenv $WORKSPACE/testapi_venv
+source $WORKSPACE/testapi_venv/bin/activate
+
+# Swgger Codegen Tool
+url="http://repo1.maven.org/maven2/io/swagger/swagger-codegen-cli/2.2.1/swagger-codegen-cli-2.2.1.jar"
+
+# Check for jar file locally and in the repo
+if [ ! -f swagger-codegen-cli.jar ];
+then
+    wget http://repo1.maven.org/maven2/io/swagger/swagger-codegen-cli/2.2.1/swagger-codegen-cli-2.2.1.jar -O swagger-codegen-cli.jar
+fi
+
+# Install Pre-requistics
+pip install requests
+
+python ./utils/test/testapi/htmlize/htmlize.py -o ${WORKSPACE}/
diff --git a/utils/test/testapi/htmlize/htmlize.py b/utils/test/testapi/htmlize/htmlize.py
new file mode 100644 (file)
index 0000000..b8c4fb4
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import argparse
+import requests
+import json
+import os
+
+
+def main(args):
+
+    # Merging two specs
+    api_response = requests.get(args.api_declaration_url)
+    api_response = json.loads(api_response.content)
+    resource_response = requests.get(args.resource_listing_url)
+    resource_response = json.loads(resource_response.content)
+    resource_response['models'] = api_response['models']
+    resource_response['apis'] = api_response['apis']
+
+    # Storing the swagger specs
+    with open('specs.json', 'w') as outfile:
+        json.dump(resource_response, outfile)
+
+    # Generating html page
+    cmd = 'java -jar swagger-codegen-cli.jar generate \
+        -i specs.json -l html2 -o %s' % (args.output_directory)
+    if os.system(cmd) == 0:
+        exit(0)
+    else:
+        exit(1)
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Create \
+                                      Swagger Spec documentation')
+    parser.add_argument('-ru', '--resource-listing-url',
+                        type=str,
+                        required=False,
+                        default=('http://testresults.opnfv.org'
+                                 '/test/swagger/spec.json'),
+                        help='Resource Listing Spec File')
+    parser.add_argument('-au', '--api-declaration-url',
+                        type=str,
+                        required=False,
+                        default=('http://testresults.opnfv.org'
+                                 '/test/swagger/spec'),
+                        help='API Declaration Spec File')
+    parser.add_argument('-o', '--output-directory',
+                        required=True,
+                        default='./',
+                        help='Output Directory where the \
+                                file should be stored')
+    main(parser.parse_args())
diff --git a/utils/test/testapi/htmlize/push-doc-artifact.sh b/utils/test/testapi/htmlize/push-doc-artifact.sh
new file mode 100644 (file)
index 0000000..4cf1988
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -e
+set -o pipefail
+
+export PATH=$PATH:/usr/local/bin/
+
+project=$PROJECT
+workspace=$WORKSPACE
+artifact_dir="$project/docs"
+
+set +e
+gsutil&>/dev/null
+if [ $? != 0 ]; then
+    echo "Not possible to push results to artifact: gsutil not installed"
+    exit 1
+else
+    gsutil ls gs://artifacts.opnfv.org/"$project"/ &>/dev/null
+    if [ $? != 0 ]; then
+        echo "Not possible to push results to artifact: gsutil not installed."
+        exit 1
+    else
+        echo "Uploading document to artifact $artifact_dir"
+        gsutil cp "$workspace"/index.html gs://artifacts.opnfv.org/"$artifact_dir"/testapi.html >/dev/null 2>&1
+        echo "Document can be found at http://artifacts.opnfv.org/releng/docs/testapi.html"
+    fi
+fi
index 43229ea..bf828b5 100755 (executable)
@@ -10,11 +10,22 @@ usage:
 where:
     -h|--help         show this help text"
 
-if [[ $(whoami) != "root" ]]; then
-    echo "Error: This script must be run as root!"
-    exit 1
+# Ref :-  https://openstack.nimeyo.com/87286/openstack-packaging-all-definition-data-files-config-setup
+
+if [ -z "$VIRTUAL_ENV" ];
+then
+    if [[ $(whoami) != "root" ]];
+    then
+        echo "Error: This script must be run as root!"
+        exit 1
+    fi
+else
+    sed -i -e 's#/etc/opnfv_testapi =#etc/opnfv_testapi =#g' setup.cfg
 fi
 
 cp -fr 3rd_party/static opnfv_testapi/tornado_swagger
 python setup.py install
 rm -fr opnfv_testapi/tornado_swagger/static
+if [ ! -z "$VIRTUAL_ENV" ]; then
+    sed -i -e 's#etc/opnfv_testapi =#/etc/opnfv_testapi =#g' setup.cfg
+fi
\ No newline at end of file
index c3d7346..fa2b722 100644 (file)
@@ -30,37 +30,43 @@ TODOs :
 """
 
 import argparse
+import sys
 
-import tornado.ioloop
 import motor
+import tornado.ioloop
 
-from opnfv_testapi.common.config import APIConfig
-from opnfv_testapi.tornado_swagger import swagger
+from opnfv_testapi.common import config
 from opnfv_testapi.router import url_mappings
+from opnfv_testapi.tornado_swagger import swagger
+
+CONF = None
+
 
-# optionally get config file from command line
-parser = argparse.ArgumentParser()
-parser.add_argument("-c", "--config-file", dest='config_file',
-                    help="Config file location")
-args = parser.parse_args()
-CONF = APIConfig().parse(args.config_file)
+def parse_config(argv=[]):
+    global CONF
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-c", "--config-file", dest='config_file',
+                        help="Config file location")
+    args = parser.parse_args(argv)
+    CONF = config.APIConfig().parse(args.config_file)
 
-# connecting to MongoDB server, and choosing database
-client = motor.MotorClient(CONF.mongo_url)
-db = client[CONF.mongo_dbname]
 
-swagger.docs(base_url=CONF.swagger_base_url)
+def get_db():
+    return motor.MotorClient(CONF.mongo_url)[CONF.mongo_dbname]
 
 
 def make_app():
+    swagger.docs(base_url=CONF.swagger_base_url)
     return swagger.Application(
         url_mappings.mappings,
-        db=db,
+        db=get_db(),
         debug=CONF.api_debug_on,
+        auth=CONF.api_authenticate_on
     )
 
 
 def main():
+    parse_config(sys.argv[1:])
     application = make_app()
     application.listen(CONF.api_port)
     tornado.ioloop.IOLoop.current().start()
index ecab88a..362fca6 100644 (file)
@@ -7,9 +7,8 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 # feng.xiaowei@zte.com.cn remove prepare_put_request            5-30-2016
 ##############################################################################
-
-
-from ConfigParser import SafeConfigParser, NoOptionError
+import ConfigParser
+import os
 
 
 class ParseError(Exception):
@@ -24,32 +23,38 @@ class ParseError(Exception):
         return 'error parsing config file : %s' % self.msg
 
 
-class APIConfig:
+class APIConfig(object):
     """
     The purpose of this class is to load values correctly from the config file.
     Each key is declared as an attribute in __init__() and linked in parse()
     """
 
     def __init__(self):
-        self._default_config_location = "/etc/opnfv_testapi/config.ini"
+        self._set_default_config()
         self.mongo_url = None
         self.mongo_dbname = None
         self.api_port = None
         self.api_debug_on = None
+        self.api_authenticate_on = None
         self._parser = None
         self.swagger_base_url = None
 
+    def _set_default_config(self):
+        venv = os.getenv('VIRTUAL_ENV')
+        self._default_config = os.path.join('/' if not venv else venv,
+                                            'etc/opnfv_testapi/config.ini')
+
     def _get_parameter(self, section, param):
         try:
             return self._parser.get(section, param)
-        except NoOptionError:
-            raise ParseError("[%s.%s] parameter not found" % (section, param))
+        except ConfigParser.NoOptionError:
+            raise ParseError("No parameter: [%s.%s]" % (section, param))
 
     def _get_int_parameter(self, section, param):
         try:
             return int(self._get_parameter(section, param))
         except ValueError:
-            raise ParseError("[%s.%s] not an int" % (section, param))
+            raise ParseError("Not int: [%s.%s]" % (section, param))
 
     def _get_bool_parameter(self, section, param):
         result = self._get_parameter(section, param)
@@ -59,37 +64,30 @@ class APIConfig:
             return False
 
         raise ParseError(
-            "[%s.%s : %s] not a boolean" % (section, param, result))
+            "Not boolean: [%s.%s : %s]" % (section, param, result))
 
     @staticmethod
     def parse(config_location=None):
         obj = APIConfig()
 
         if config_location is None:
-            config_location = obj._default_config_location
+            config_location = obj._default_config
 
-        obj._parser = SafeConfigParser()
-        obj._parser.read(config_location)
-        if not obj._parser:
+        if not os.path.exists(config_location):
             raise ParseError("%s not found" % config_location)
 
+        obj._parser = ConfigParser.SafeConfigParser()
+        obj._parser.read(config_location)
+
         # Linking attributes to keys from file with their sections
         obj.mongo_url = obj._get_parameter("mongo", "url")
         obj.mongo_dbname = obj._get_parameter("mongo", "dbname")
 
         obj.api_port = obj._get_int_parameter("api", "port")
         obj.api_debug_on = obj._get_bool_parameter("api", "debug")
+        obj.api_authenticate_on = obj._get_bool_parameter("api",
+                                                          "authenticate")
+
         obj.swagger_base_url = obj._get_parameter("swagger", "base_url")
 
         return obj
-
-    def __str__(self):
-        return "mongo_url = %s \n" \
-               "mongo_dbname = %s \n" \
-               "api_port = %s \n" \
-               "api_debug_on = %s \n" \
-               "swagger_base_url = %s \n" % (self.mongo_url,
-                                             self.mongo_dbname,
-                                             self.api_port,
-                                             self.api_debug_on,
-                                             self.swagger_base_url)
diff --git a/utils/test/testapi/opnfv_testapi/common/message.py b/utils/test/testapi/opnfv_testapi/common/message.py
new file mode 100644 (file)
index 0000000..98536ff
--- /dev/null
@@ -0,0 +1,46 @@
+##############################################################################
+# Copyright (c) 2017 ZTE Corp
+# feng.xiaowei@zte.com.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+not_found_base = 'Could Not Found'
+exist_base = 'Already Exists'
+
+
+def no_body():
+    return 'No Body'
+
+
+def not_found(key, value):
+    return '{} {} [{}]'.format(not_found_base, key, value)
+
+
+def missing(name):
+    return '{} Missing'.format(name)
+
+
+def exist(key, value):
+    return '{} [{}] {}'.format(key, value, exist_base)
+
+
+def bad_format(error):
+    return 'Bad Format [{}]'.format(error)
+
+
+def unauthorized():
+    return 'No Authentication Header'
+
+
+def invalid_token():
+    return 'Invalid Token'
+
+
+def no_update():
+    return 'Nothing to update'
+
+
+def must_int(name):
+    return '{} must be int'.format(name)
diff --git a/utils/test/testapi/opnfv_testapi/common/raises.py b/utils/test/testapi/opnfv_testapi/common/raises.py
new file mode 100644 (file)
index 0000000..ec6b8a5
--- /dev/null
@@ -0,0 +1,39 @@
+##############################################################################
+# Copyright (c) 2017 ZTE Corp
+# feng.xiaowei@zte.com.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import httplib
+
+from tornado import web
+
+
+class Raiser(object):
+    code = httplib.OK
+
+    def __init__(self, reason):
+        raise web.HTTPError(self.code, reason)
+
+
+class BadRequest(Raiser):
+    code = httplib.BAD_REQUEST
+
+
+class Forbidden(Raiser):
+    code = httplib.FORBIDDEN
+
+
+class NotFound(Raiser):
+    code = httplib.NOT_FOUND
+
+
+class Unauthorized(Raiser):
+    code = httplib.UNAUTHORIZED
+
+
+class CodeTBD(object):
+    def __init__(self, code, reason):
+        raise web.HTTPError(code, reason)
index 5059f5d..522bbe7 100644 (file)
 # feng.xiaowei@zte.com.cn remove DashboardHandler            5-30-2016
 ##############################################################################
 
-import json
 from datetime import datetime
+import functools
+import json
 
 from tornado import gen
-from tornado.web import RequestHandler, asynchronous, HTTPError
+from tornado import web
 
-from models import CreateResponse
-from opnfv_testapi.common.constants import DEFAULT_REPRESENTATION, \
-    HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_FORBIDDEN
+import models
+from opnfv_testapi.common import message
+from opnfv_testapi.common import raises
 from opnfv_testapi.tornado_swagger import swagger
 
+DEFAULT_REPRESENTATION = "application/json"
+
 
-class GenericApiHandler(RequestHandler):
+class GenericApiHandler(web.RequestHandler):
     def __init__(self, application, request, **kwargs):
         super(GenericApiHandler, self).__init__(application, request, **kwargs)
         self.db = self.settings["db"]
@@ -43,6 +46,8 @@ class GenericApiHandler(RequestHandler):
         self.db_pods = 'pods'
         self.db_testcases = 'testcases'
         self.db_results = 'results'
+        self.db_scenarios = 'scenarios'
+        self.auth = self.settings["auth"]
 
     def prepare(self):
         if self.request.method != "GET" and self.request.method != "DELETE":
@@ -52,9 +57,7 @@ class GenericApiHandler(RequestHandler):
                     try:
                         self.json_args = json.loads(self.request.body)
                     except (ValueError, KeyError, TypeError) as error:
-                        raise HTTPError(HTTP_BAD_REQUEST,
-                                        "Bad Json format [{}]".
-                                        format(error))
+                        raises.BadRequest(message.bad_format(str(error)))
 
     def finish_request(self, json_object=None):
         if json_object:
@@ -64,28 +67,44 @@ class GenericApiHandler(RequestHandler):
 
     def _create_response(self, resource):
         href = self.request.full_url() + '/' + str(resource)
-        return CreateResponse(href=href).format()
+        return models.CreateResponse(href=href).format()
 
     def format_data(self, data):
         cls_data = self.table_cls.from_dict(data)
         return cls_data.format_http()
 
-    @asynchronous
-    @gen.coroutine
+    def authenticate(method):
+        @web.asynchronous
+        @gen.coroutine
+        @functools.wraps(method)
+        def wrapper(self, *args, **kwargs):
+            if self.auth:
+                try:
+                    token = self.request.headers['X-Auth-Token']
+                except KeyError:
+                    raises.Unauthorized(message.unauthorized())
+                query = {'access_token': token}
+                check = yield self._eval_db_find_one(query, 'tokens')
+                if not check:
+                    raises.Forbidden(message.invalid_token())
+            ret = yield gen.coroutine(method)(self, *args, **kwargs)
+            raise gen.Return(ret)
+        return wrapper
+
+    @authenticate
     def _create(self, miss_checks, db_checks, **kwargs):
         """
         :param miss_checks: [miss1, miss2]
         :param db_checks: [(table, exist, query, error)]
         """
         if self.json_args is None:
-            raise HTTPError(HTTP_BAD_REQUEST, "no body")
+            raises.BadRequest(message.no_body())
 
         data = self.table_cls.from_dict(self.json_args)
         for miss in miss_checks:
             miss_data = data.__getattribute__(miss)
             if miss_data is None or miss_data == '':
-                raise HTTPError(HTTP_BAD_REQUEST,
-                                '{} missing'.format(miss))
+                raises.BadRequest(message.missing(miss))
 
         for k, v in kwargs.iteritems():
             data.__setattr__(k, v)
@@ -93,8 +112,8 @@ class GenericApiHandler(RequestHandler):
         for table, exist, query, error in db_checks:
             check = yield self._eval_db_find_one(query(data), table)
             if (exist and not check) or (not exist and check):
-                code, message = error(data)
-                raise HTTPError(code, message)
+                code, msg = error(data)
+                raises.CodeTBD(code, msg)
 
         if self.table != 'results':
             data.creation_date = datetime.now()
@@ -106,7 +125,7 @@ class GenericApiHandler(RequestHandler):
             resource = _id
         self.finish_request(self._create_response(resource))
 
-    @asynchronous
+    @web.asynchronous
     @gen.coroutine
     def _list(self, query=None, res_op=None, *args, **kwargs):
         if query is None:
@@ -125,40 +144,32 @@ class GenericApiHandler(RequestHandler):
             res = res_op(data, *args)
         self.finish_request(res)
 
-    @asynchronous
+    @web.asynchronous
     @gen.coroutine
     def _get_one(self, query):
         data = yield self._eval_db_find_one(query)
         if data is None:
-            raise HTTPError(HTTP_NOT_FOUND,
-                            "[{}] not exist in table [{}]"
-                            .format(query, self.table))
+            raises.NotFound(message.not_found(self.table, query))
         self.finish_request(self.format_data(data))
 
-    @asynchronous
-    @gen.coroutine
+    @authenticate
     def _delete(self, query):
         data = yield self._eval_db_find_one(query)
         if data is None:
-            raise HTTPError(HTTP_NOT_FOUND,
-                            "[{}] not exit in table [{}]"
-                            .format(query, self.table))
+            raises.NotFound(message.not_found(self.table, query))
 
         yield self._eval_db(self.table, 'remove', query)
         self.finish_request()
 
-    @asynchronous
-    @gen.coroutine
+    @authenticate
     def _update(self, query, db_keys):
         if self.json_args is None:
-            raise HTTPError(HTTP_BAD_REQUEST, "No payload")
+            raises.BadRequest(message.no_body())
 
         # check old data exist
         from_data = yield self._eval_db_find_one(query)
         if from_data is None:
-            raise HTTPError(HTTP_NOT_FOUND,
-                            "{} could not be found in table [{}]"
-                            .format(query, self.table))
+            raises.NotFound(message.not_found(self.table, query))
 
         data = self.table_cls.from_dict(from_data)
         # check new data exist
@@ -166,13 +177,10 @@ class GenericApiHandler(RequestHandler):
         if not equal:
             to_data = yield self._eval_db_find_one(new_query)
             if to_data is not None:
-                raise HTTPError(HTTP_FORBIDDEN,
-                                "{} already exists in table [{}]"
-                                .format(new_query, self.table))
+                raises.Forbidden(message.exist(self.table, new_query))
 
         # we merge the whole document """
-        edit_request = data.format()
-        edit_request.update(self._update_requests(data))
+        edit_request = self._update_requests(data)
 
         """ Updating the DB """
         yield self._eval_db(self.table, 'update', query, edit_request,
@@ -186,8 +194,11 @@ class GenericApiHandler(RequestHandler):
             request = self._update_request(request, k, v,
                                            data.__getattribute__(k))
         if not request:
-            raise HTTPError(HTTP_FORBIDDEN, "Nothing to update")
-        return request
+            raises.Forbidden(message.no_update())
+
+        edit_request = data.format()
+        edit_request.update(request)
+        return edit_request
 
     @staticmethod
     def _update_request(edit_request, key, new_value, old_value):
@@ -227,7 +238,7 @@ class GenericApiHandler(RequestHandler):
 
 
 class VersionHandler(GenericApiHandler):
-    @swagger.operation(nickname='list')
+    @swagger.operation(nickname='listAllVersions')
     def get(self):
         """
             @description: list all supported versions
index e79308b..0ea482f 100644 (file)
-##############################################################################\r
-# Copyright (c) 2015 Orange\r
-# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com\r
-# All rights reserved. This program and the accompanying materials\r
-# are made available under the terms of the Apache License, Version 2.0\r
-# which accompanies this distribution, and is available at\r
-# http://www.apache.org/licenses/LICENSE-2.0\r
-# feng.xiaowei@zte.com.cn  mv Pod to pod_models.py                 5-18-2016\r
-# feng.xiaowei@zte.com.cn  add MetaCreateResponse/MetaGetResponse  5-18-2016\r
-# feng.xiaowei@zte.com.cn  mv TestProject to project_models.py     5-19-2016\r
-# feng.xiaowei@zte.com.cn  delete meta class                       5-19-2016\r
-# feng.xiaowei@zte.com.cn  add CreateResponse                      5-19-2016\r
-# feng.xiaowei@zte.com.cn  mv TestCase to testcase_models.py       5-20-2016\r
-# feng.xiaowei@zte.com.cn  mv TestResut to result_models.py        5-23-2016\r
-##############################################################################\r
-from opnfv_testapi.tornado_swagger import swagger\r
-\r
-\r
-@swagger.model()\r
-class CreateResponse(object):\r
-    def __init__(self, href=''):\r
-        self.href = href\r
-\r
-    @staticmethod\r
-    def from_dict(res_dict):\r
-        if res_dict is None:\r
-            return None\r
-\r
-        res = CreateResponse()\r
-        res.href = res_dict.get('href')\r
-        return res\r
-\r
-    def format(self):\r
-        return {'href': self.href}\r
-\r
-\r
-@swagger.model()\r
-class Versions(object):\r
-    """\r
-        @property versions:\r
-        @ptype versions: C{list} of L{Version}\r
-    """\r
-    def __init__(self):\r
-        self.versions = list()\r
-\r
-    @staticmethod\r
-    def from_dict(res_dict):\r
-        if res_dict is None:\r
-            return None\r
-\r
-        res = Versions()\r
-        for version in res_dict.get('versions'):\r
-            res.versions.append(Version.from_dict(version))\r
-        return res\r
-\r
-\r
-@swagger.model()\r
-class Version(object):\r
-    def __init__(self, version=None, description=None):\r
-        self.version = version\r
-        self.description = description\r
-\r
-    @staticmethod\r
-    def from_dict(a_dict):\r
-        if a_dict is None:\r
-            return None\r
-\r
-        ver = Version()\r
-        ver.version = a_dict.get('version')\r
-        ver.description = str(a_dict.get('description'))\r
-        return ver\r
+##############################################################################
+# Copyright (c) 2015 Orange
+# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+# feng.xiaowei@zte.com.cn  mv Pod to pod_models.py                 5-18-2016
+# feng.xiaowei@zte.com.cn  add MetaCreateResponse/MetaGetResponse  5-18-2016
+# feng.xiaowei@zte.com.cn  mv TestProject to project_models.py     5-19-2016
+# feng.xiaowei@zte.com.cn  delete meta class                       5-19-2016
+# feng.xiaowei@zte.com.cn  add CreateResponse                      5-19-2016
+# feng.xiaowei@zte.com.cn  mv TestCase to testcase_models.py       5-20-2016
+# feng.xiaowei@zte.com.cn  mv TestResut to result_models.py        5-23-2016
+# feng.xiaowei@zte.com.cn  add ModelBase                           12-20-2016
+##############################################################################
+import copy
+import ast
+
+
+from opnfv_testapi.tornado_swagger import swagger
+
+
+class ModelBase(object):
+
+    def format(self):
+        return self._format(['_id'])
+
+    def format_http(self):
+        return self._format([])
+
+    @classmethod
+    def from_dict(cls, a_dict):
+        if a_dict is None:
+            return None
+
+        attr_parser = cls.attr_parser()
+        t = cls()
+        for k, v in a_dict.iteritems():
+            value = v
+            if isinstance(v, dict) and k in attr_parser:
+                value = attr_parser[k].from_dict(v)
+            elif isinstance(v, list) and k in attr_parser:
+                value = []
+                for item in v:
+                    value.append(attr_parser[k].from_dict(item))
+
+            t.__setattr__(k, value)
+
+        return t
+
+    @staticmethod
+    def attr_parser():
+        return {}
+
+    def _format(self, excludes):
+        new_obj = copy.deepcopy(self)
+        dicts = new_obj.__dict__
+        for k in dicts.keys():
+            if k in excludes:
+                del dicts[k]
+            elif dicts[k]:
+                dicts[k] = self._obj_format(dicts[k])
+        return dicts
+
+    def _obj_format(self, obj):
+        if self._has_format(obj):
+            obj = obj.format()
+        elif isinstance(obj, unicode):
+            try:
+                obj = self._obj_format(ast.literal_eval(obj))
+            except:
+                try:
+                    obj = str(obj)
+                except:
+                    obj = obj
+        elif isinstance(obj, list):
+            hs = list()
+            for h in obj:
+                hs.append(self._obj_format(h))
+            obj = hs
+        elif not isinstance(obj, (str, int, float, dict)):
+            obj = str(obj)
+        return obj
+
+    @staticmethod
+    def _has_format(obj):
+        return not isinstance(obj, (str, unicode)) and hasattr(obj, 'format')
+
+
+@swagger.model()
+class CreateResponse(ModelBase):
+    def __init__(self, href=''):
+        self.href = href
+
+
+@swagger.model()
+class Versions(ModelBase):
+    """
+        @property versions:
+        @ptype versions: C{list} of L{Version}
+    """
+
+    def __init__(self):
+        self.versions = list()
+
+    @staticmethod
+    def attr_parser():
+        return {'versions': Version}
+
+
+@swagger.model()
+class Version(ModelBase):
+    def __init__(self, version=None, description=None):
+        self.version = version
+        self.description = description
index 8f44439..2c303c9 100644 (file)
@@ -6,21 +6,23 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import httplib
+
+import handlers
+from opnfv_testapi.common import message
 from opnfv_testapi.tornado_swagger import swagger
-from handlers import GenericApiHandler
-from pod_models import Pod
-from opnfv_testapi.common.constants import HTTP_FORBIDDEN
+import pod_models
 
 
-class GenericPodHandler(GenericApiHandler):
+class GenericPodHandler(handlers.GenericApiHandler):
     def __init__(self, application, request, **kwargs):
         super(GenericPodHandler, self).__init__(application, request, **kwargs)
         self.table = 'pods'
-        self.table_cls = Pod
+        self.table_cls = pod_models.Pod
 
 
 class PodCLHandler(GenericPodHandler):
-    @swagger.operation(nickname='list-all')
+    @swagger.operation(nickname='listAllPods')
     def get(self):
         """
             @description: list all pods
@@ -29,7 +31,7 @@ class PodCLHandler(GenericPodHandler):
         """
         self._list()
 
-    @swagger.operation(nickname='create')
+    @swagger.operation(nickname='createPod')
     def post(self):
         """
             @description: create a pod
@@ -45,8 +47,7 @@ class PodCLHandler(GenericPodHandler):
             return {'name': data.name}
 
         def error(data):
-            message = '{} already exists as a pod'.format(data.name)
-            return HTTP_FORBIDDEN, message
+            return httplib.FORBIDDEN, message.exist('pod', data.name)
 
         miss_checks = ['name']
         db_checks = [(self.table, False, query, error)]
@@ -54,7 +55,7 @@ class PodCLHandler(GenericPodHandler):
 
 
 class PodGURHandler(GenericPodHandler):
-    @swagger.operation(nickname='get-one')
+    @swagger.operation(nickname='getPodByName')
     def get(self, pod_name):
         """
             @description: get a single pod by pod_name
index 7231806..26a9e67 100644 (file)
@@ -6,8 +6,10 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import models
 from opnfv_testapi.tornado_swagger import swagger
 
+
 # name: name of the POD e.g. zte-1
 # mode: metal or virtual
 # details: any detail
@@ -15,58 +17,29 @@ from opnfv_testapi.tornado_swagger import swagger
 
 
 @swagger.model()
-class PodCreateRequest(object):
+class PodCreateRequest(models.ModelBase):
     def __init__(self, name, mode='', details='', role=""):
         self.name = name
         self.mode = mode
         self.details = details
         self.role = role
 
-    def format(self):
-        return {
-            "name": self.name,
-            "mode": self.mode,
-            "details": self.details,
-            "role": self.role,
-        }
-
 
 @swagger.model()
-class Pod(PodCreateRequest):
+class Pod(models.ModelBase):
     def __init__(self,
                  name='', mode='', details='',
                  role="", _id='', create_date=''):
-        super(Pod, self).__init__(name, mode, details, role)
+        self.name = name
+        self.mode = mode
+        self.details = details
+        self.role = role
         self._id = _id
         self.creation_date = create_date
 
-    @staticmethod
-    def from_dict(pod_dict):
-        if pod_dict is None:
-            return None
-
-        p = Pod()
-        p._id = pod_dict.get('_id')
-        p.creation_date = str(pod_dict.get('creation_date'))
-        p.name = pod_dict.get('name')
-        p.mode = pod_dict.get('mode')
-        p.details = pod_dict.get('details')
-        p.role = pod_dict.get('role')
-        return p
-
-    def format(self):
-        f = super(Pod, self).format()
-        f['creation_date'] = str(self.creation_date)
-        return f
-
-    def format_http(self):
-        f = self.format()
-        f['_id'] = str(self._id)
-        return f
-
 
 @swagger.model()
-class Pods(object):
+class Pods(models.ModelBase):
     """
         @property pods:
         @ptype pods: C{list} of L{Pod}
@@ -75,11 +48,5 @@ class Pods(object):
         self.pods = list()
 
     @staticmethod
-    def from_dict(res_dict):
-        if res_dict is None:
-            return None
-
-        res = Pods()
-        for pod in res_dict.get('pods'):
-            res.pods.append(Pod.from_dict(pod))
-        return res
+    def attr_parser():
+        return {'pods': Pod}
index 1e9a972..59e0b88 100644 (file)
@@ -6,23 +6,25 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import httplib
+
+import handlers
+from opnfv_testapi.common import message
 from opnfv_testapi.tornado_swagger import swagger
-from handlers import GenericApiHandler
-from opnfv_testapi.common.constants import HTTP_FORBIDDEN
-from project_models import Project
+import project_models
 
 
-class GenericProjectHandler(GenericApiHandler):
+class GenericProjectHandler(handlers.GenericApiHandler):
     def __init__(self, application, request, **kwargs):
         super(GenericProjectHandler, self).__init__(application,
                                                     request,
                                                     **kwargs)
         self.table = 'projects'
-        self.table_cls = Project
+        self.table_cls = project_models.Project
 
 
 class ProjectCLHandler(GenericProjectHandler):
-    @swagger.operation(nickname="list-all")
+    @swagger.operation(nickname="listAllProjects")
     def get(self):
         """
             @description: list all projects
@@ -31,7 +33,7 @@ class ProjectCLHandler(GenericProjectHandler):
         """
         self._list()
 
-    @swagger.operation(nickname="create")
+    @swagger.operation(nickname="createProject")
     def post(self):
         """
             @description: create a project
@@ -47,8 +49,7 @@ class ProjectCLHandler(GenericProjectHandler):
             return {'name': data.name}
 
         def error(data):
-            message = '{} already exists as a project'.format(data.name)
-            return HTTP_FORBIDDEN, message
+            return httplib.FORBIDDEN, message.exist('project', data.name)
 
         miss_checks = ['name']
         db_checks = [(self.table, False, query, error)]
@@ -56,7 +57,7 @@ class ProjectCLHandler(GenericProjectHandler):
 
 
 class ProjectGURHandler(GenericProjectHandler):
-    @swagger.operation(nickname='get-one')
+    @swagger.operation(nickname='getProjectByName')
     def get(self, project_name):
         """
             @description: get a single project by project_name
@@ -66,7 +67,7 @@ class ProjectGURHandler(GenericProjectHandler):
         """
         self._get_one({'name': project_name})
 
-    @swagger.operation(nickname="update")
+    @swagger.operation(nickname="updateProjectByName")
     def put(self, project_name):
         """
             @description: update a single project by project_name
@@ -82,7 +83,7 @@ class ProjectGURHandler(GenericProjectHandler):
         db_keys = ['name']
         self._update(query, db_keys)
 
-    @swagger.operation(nickname='delete')
+    @swagger.operation(nickname='deleteProjectByName')
     def delete(self, project_name):
         """
             @description: delete a project by project_name
index f70630c..f7323c1 100644 (file)
@@ -6,37 +6,26 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import models
 from opnfv_testapi.tornado_swagger import swagger
 
 
 @swagger.model()
-class ProjectCreateRequest(object):
+class ProjectCreateRequest(models.ModelBase):
     def __init__(self, name, description=''):
         self.name = name
         self.description = description
 
-    def format(self):
-        return {
-            "name": self.name,
-            "description": self.description,
-        }
-
 
 @swagger.model()
-class ProjectUpdateRequest(object):
+class ProjectUpdateRequest(models.ModelBase):
     def __init__(self, name='', description=''):
         self.name = name
         self.description = description
 
-    def format(self):
-        return {
-            "name": self.name,
-            "description": self.description,
-        }
-
 
 @swagger.model()
-class Project(object):
+class Project(models.ModelBase):
     def __init__(self,
                  name=None, _id=None, description=None, create_date=None):
         self._id = _id
@@ -44,38 +33,9 @@ class Project(object):
         self.description = description
         self.creation_date = create_date
 
-    @staticmethod
-    def from_dict(res_dict):
-
-        if res_dict is None:
-            return None
-
-        t = Project()
-        t._id = res_dict.get('_id')
-        t.creation_date = res_dict.get('creation_date')
-        t.name = res_dict.get('name')
-        t.description = res_dict.get('description')
-
-        return t
-
-    def format(self):
-        return {
-            "name": self.name,
-            "description": self.description,
-            "creation_date": str(self.creation_date)
-        }
-
-    def format_http(self):
-        return {
-            "_id": str(self._id),
-            "name": self.name,
-            "description": self.description,
-            "creation_date": str(self.creation_date),
-        }
-
 
 @swagger.model()
-class Projects(object):
+class Projects(models.ModelBase):
     """
         @property projects:
         @ptype projects: C{list} of L{Project}
@@ -84,11 +44,5 @@ class Projects(object):
         self.projects = list()
 
     @staticmethod
-    def from_dict(res_dict):
-        if res_dict is None:
-            return None
-
-        res = Projects()
-        for project in res_dict.get('projects'):
-            res.projects.append(Project.from_dict(project))
-        return res
+    def attr_parser():
+        return {'projects': Project}
index 400b84a..fb5ed9e 100644 (file)
@@ -6,30 +6,32 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-from datetime import datetime, timedelta
+from datetime import datetime
+from datetime import timedelta
+import httplib
 
-from bson.objectid import ObjectId
-from tornado.web import HTTPError
+from bson import objectid
 
-from opnfv_testapi.common.constants import HTTP_BAD_REQUEST, HTTP_NOT_FOUND
-from opnfv_testapi.resources.handlers import GenericApiHandler
-from opnfv_testapi.resources.result_models import TestResult
+from opnfv_testapi.common import message
+from opnfv_testapi.common import raises
+from opnfv_testapi.resources import handlers
+from opnfv_testapi.resources import result_models
 from opnfv_testapi.tornado_swagger import swagger
 
 
-class GenericResultHandler(GenericApiHandler):
+class GenericResultHandler(handlers.GenericApiHandler):
     def __init__(self, application, request, **kwargs):
         super(GenericResultHandler, self).__init__(application,
                                                    request,
                                                    **kwargs)
         self.table = self.db_results
-        self.table_cls = TestResult
+        self.table_cls = result_models.TestResult
 
     def get_int(self, key, value):
         try:
             value = int(value)
         except:
-            raise HTTPError(HTTP_BAD_REQUEST, '{} must be int'.format(key))
+            raises.BadRequest(message.must_int(key))
         return value
 
     def set_query(self):
@@ -52,7 +54,7 @@ class GenericResultHandler(GenericApiHandler):
 
 
 class ResultsCLHandler(GenericResultHandler):
-    @swagger.operation(nickname="list-all")
+    @swagger.operation(nickname="queryTestResults")
     def get(self):
         """
             @description: Retrieve result(s) for a test project
@@ -127,7 +129,7 @@ class ResultsCLHandler(GenericResultHandler):
 
         self._list(self.set_query(), sort=[('start_date', -1)], last=last)
 
-    @swagger.operation(nickname="create")
+    @swagger.operation(nickname="createTestResult")
     def post(self):
         """
             @description: create a test result
@@ -143,23 +145,21 @@ class ResultsCLHandler(GenericResultHandler):
             return {'name': data.pod_name}
 
         def pod_error(data):
-            message = 'Could not find pod [{}]'.format(data.pod_name)
-            return HTTP_NOT_FOUND, message
+            return httplib.FORBIDDEN, message.not_found('pod', data.pod_name)
 
         def project_query(data):
             return {'name': data.project_name}
 
         def project_error(data):
-            message = 'Could not find project [{}]'.format(data.project_name)
-            return HTTP_NOT_FOUND, message
+            return httplib.FORBIDDEN, message.not_found('project',
+                                                        data.project_name)
 
         def testcase_query(data):
             return {'project_name': data.project_name, 'name': data.case_name}
 
         def testcase_error(data):
-            message = 'Could not find testcase [{}] in project [{}]'\
-                .format(data.case_name, data.project_name)
-            return HTTP_NOT_FOUND, message
+            return httplib.FORBIDDEN, message.not_found('testcase',
+                                                        data.case_name)
 
         miss_checks = ['pod_name', 'project_name', 'case_name']
         db_checks = [('pods', True, pod_query, pod_error),
@@ -169,7 +169,7 @@ class ResultsCLHandler(GenericResultHandler):
 
 
 class ResultsGURHandler(GenericResultHandler):
-    @swagger.operation(nickname='get-one')
+    @swagger.operation(nickname='getTestResultById')
     def get(self, result_id):
         """
             @description: get a single result by result_id
@@ -178,10 +178,10 @@ class ResultsGURHandler(GenericResultHandler):
             @raise 404: test result not exist
         """
         query = dict()
-        query["_id"] = ObjectId(result_id)
+        query["_id"] = objectid.ObjectId(result_id)
         self._get_one(query)
 
-    @swagger.operation(nickname="update")
+    @swagger.operation(nickname="updateTestResultById")
     def put(self, result_id):
         """
             @description: update a single result by _id
@@ -193,6 +193,6 @@ class ResultsGURHandler(GenericResultHandler):
             @raise 404: result not exist
             @raise 403: nothing to update
         """
-        query = {'_id': ObjectId(result_id)}
+        query = {'_id': objectid.ObjectId(result_id)}
         db_keys = []
         self._update(query, db_keys)
index f73f5c6..50445fc 100644 (file)
@@ -6,11 +6,12 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import models
 from opnfv_testapi.tornado_swagger import swagger
 
 
 @swagger.model()
-class TIHistory(object):
+class TIHistory(models.ModelBase):
     """
         @ptype step: L{float}
     """
@@ -18,22 +19,9 @@ class TIHistory(object):
         self.date = date
         self.step = step
 
-    def format(self):
-        return {
-            "date": self.date,
-            "step": self.step
-        }
-
-    @staticmethod
-    def from_dict(a_dict):
-        if a_dict is None:
-            return None
-
-        return TIHistory(a_dict.get('date'), a_dict.get('step'))
-
 
 @swagger.model()
-class TI(object):
+class TI(models.ModelBase):
     """
         @property histories: trust_indicator update histories
         @ptype histories: C{list} of L{TIHistory}
@@ -43,31 +31,13 @@ class TI(object):
         self.current = current
         self.histories = list()
 
-    def format(self):
-        hs = []
-        for h in self.histories:
-            hs.append(h.format())
-
-        return {
-            "current": self.current,
-            "histories": hs
-        }
-
     @staticmethod
-    def from_dict(a_dict):
-        t = TI()
-        if a_dict:
-            t.current = a_dict.get('current')
-            if 'histories' in a_dict.keys():
-                for history in a_dict.get('histories', None):
-                    t.histories.append(TIHistory.from_dict(history))
-            else:
-                t.histories = []
-        return t
+    def attr_parser():
+        return {'histories': TIHistory}
 
 
 @swagger.model()
-class ResultCreateRequest(object):
+class ResultCreateRequest(models.ModelBase):
     """
         @property trust_indicator:
         @ptype trust_indicator: L{TI}
@@ -98,25 +68,9 @@ class ResultCreateRequest(object):
         self.criteria = criteria
         self.trust_indicator = trust_indicator if trust_indicator else TI(0)
 
-    def format(self):
-        return {
-            "pod_name": self.pod_name,
-            "project_name": self.project_name,
-            "case_name": self.case_name,
-            "installer": self.installer,
-            "version": self.version,
-            "start_date": self.start_date,
-            "stop_date": self.stop_date,
-            "details": self.details,
-            "build_tag": self.build_tag,
-            "scenario": self.scenario,
-            "criteria": self.criteria,
-            "trust_indicator": self.trust_indicator.format()
-        }
-
 
 @swagger.model()
-class ResultUpdateRequest(object):
+class ResultUpdateRequest(models.ModelBase):
     """
         @property trust_indicator:
         @ptype trust_indicator: L{TI}
@@ -124,14 +78,9 @@ class ResultUpdateRequest(object):
     def __init__(self, trust_indicator=None):
         self.trust_indicator = trust_indicator
 
-    def format(self):
-        return {
-            "trust_indicator": self.trust_indicator.format(),
-        }
-
 
 @swagger.model()
-class TestResult(object):
+class TestResult(models.ModelBase):
     """
         @property trust_indicator: used for long duration test case
         @ptype trust_indicator: L{TI}
@@ -156,63 +105,12 @@ class TestResult(object):
         self.trust_indicator = trust_indicator
 
     @staticmethod
-    def from_dict(a_dict):
-
-        if a_dict is None:
-            return None
-
-        t = TestResult()
-        t._id = a_dict.get('_id')
-        t.case_name = a_dict.get('case_name')
-        t.pod_name = a_dict.get('pod_name')
-        t.project_name = a_dict.get('project_name')
-        t.start_date = str(a_dict.get('start_date'))
-        t.stop_date = str(a_dict.get('stop_date'))
-        t.details = a_dict.get('details')
-        t.version = a_dict.get('version')
-        t.installer = a_dict.get('installer')
-        t.build_tag = a_dict.get('build_tag')
-        t.scenario = a_dict.get('scenario')
-        t.criteria = a_dict.get('criteria')
-        t.trust_indicator = TI.from_dict(a_dict.get('trust_indicator'))
-        return t
-
-    def format(self):
-        return {
-            "case_name": self.case_name,
-            "project_name": self.project_name,
-            "pod_name": self.pod_name,
-            "start_date": str(self.start_date),
-            "stop_date": str(self.stop_date),
-            "version": self.version,
-            "installer": self.installer,
-            "details": self.details,
-            "build_tag": self.build_tag,
-            "scenario": self.scenario,
-            "criteria": self.criteria,
-            "trust_indicator": self.trust_indicator.format()
-        }
-
-    def format_http(self):
-        return {
-            "_id": str(self._id),
-            "case_name": self.case_name,
-            "project_name": self.project_name,
-            "pod_name": self.pod_name,
-            "start_date": str(self.start_date),
-            "stop_date": str(self.stop_date),
-            "version": self.version,
-            "installer": self.installer,
-            "details": self.details,
-            "build_tag": self.build_tag,
-            "scenario": self.scenario,
-            "criteria": self.criteria,
-            "trust_indicator": self.trust_indicator.format()
-        }
+    def attr_parser():
+        return {'trust_indicator': TI}
 
 
 @swagger.model()
-class TestResults(object):
+class TestResults(models.ModelBase):
     """
         @property results:
         @ptype results: C{list} of L{TestResult}
@@ -221,11 +119,5 @@ class TestResults(object):
         self.results = list()
 
     @staticmethod
-    def from_dict(a_dict):
-        if a_dict is None:
-            return None
-
-        res = TestResults()
-        for result in a_dict.get('results'):
-            res.results.append(TestResult.from_dict(result))
-        return res
+    def attr_parser():
+        return {'results': TestResult}
diff --git a/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py b/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py
new file mode 100644 (file)
index 0000000..bad79fd
--- /dev/null
@@ -0,0 +1,291 @@
+import functools
+import httplib
+
+from opnfv_testapi.common import message
+from opnfv_testapi.common import raises
+from opnfv_testapi.resources import handlers
+import opnfv_testapi.resources.scenario_models as models
+from opnfv_testapi.tornado_swagger import swagger
+
+
+class GenericScenarioHandler(handlers.GenericApiHandler):
+    def __init__(self, application, request, **kwargs):
+        super(GenericScenarioHandler, self).__init__(application,
+                                                     request,
+                                                     **kwargs)
+        self.table = self.db_scenarios
+        self.table_cls = models.Scenario
+
+
+class ScenariosCLHandler(GenericScenarioHandler):
+    @swagger.operation(nickname="queryScenarios")
+    def get(self):
+        """
+            @description: Retrieve scenario(s).
+            @notes: Retrieve scenario(s)
+                Available filters for this request are :
+                 - name : scenario name
+
+                GET /scenarios?name=scenario_1
+            @param name: scenario name
+            @type name: L{string}
+            @in name: query
+            @required name: False
+            @param installer: installer type
+            @type installer: L{string}
+            @in installer: query
+            @required installer: False
+            @param version: version
+            @type version: L{string}
+            @in version: query
+            @required version: False
+            @param project: project name
+            @type project: L{string}
+            @in project: query
+            @required project: False
+            @return 200: all scenarios satisfy queries,
+                         empty list if no scenario is found
+            @rtype: L{Scenarios}
+        """
+
+        def _set_query():
+            query = dict()
+            elem_query = dict()
+            for k in self.request.query_arguments.keys():
+                v = self.get_query_argument(k)
+                if k == 'installer':
+                    elem_query["installer"] = v
+                elif k == 'version':
+                    elem_query["versions.version"] = v
+                elif k == 'project':
+                    elem_query["versions.projects.project"] = v
+                else:
+                    query[k] = v
+            if elem_query:
+                query['installers'] = {'$elemMatch': elem_query}
+            return query
+
+        self._list(_set_query())
+
+    @swagger.operation(nickname="createScenario")
+    def post(self):
+        """
+            @description: create a new scenario by name
+            @param body: scenario to be created
+            @type body: L{ScenarioCreateRequest}
+            @in body: body
+            @rtype: L{CreateResponse}
+            @return 200: scenario is created.
+            @raise 403: scenario already exists
+            @raise 400:  body or name not provided
+        """
+        def query(data):
+            return {'name': data.name}
+
+        def error(data):
+            return httplib.FORBIDDEN, message.exist('scenario', data.name)
+
+        miss_checks = ['name']
+        db_checks = [(self.table, False, query, error)]
+        self._create(miss_checks=miss_checks, db_checks=db_checks)
+
+
+class ScenarioGURHandler(GenericScenarioHandler):
+    @swagger.operation(nickname='getScenarioByName')
+    def get(self, name):
+        """
+            @description: get a single scenario by name
+            @rtype: L{Scenario}
+            @return 200: scenario exist
+            @raise 404: scenario not exist
+        """
+        self._get_one({'name': name})
+        pass
+
+    @swagger.operation(nickname="updateScenarioByName")
+    def put(self, name):
+        """
+            @description: update a single scenario by name
+            @param body: fields to be updated
+            @type body: L{ScenarioUpdateRequest}
+            @in body: body
+            @rtype: L{Scenario}
+            @return 200: update success
+            @raise 404: scenario not exist
+            @raise 403: nothing to update
+        """
+        query = {'name': name}
+        db_keys = ['name']
+        self._update(query, db_keys)
+
+    @swagger.operation(nickname="deleteScenarioByName")
+    def delete(self, name):
+        """
+        @description: delete a scenario by name
+        @return 200: delete success
+        @raise 404: scenario not exist:
+        """
+
+        query = {'name': name}
+        self._delete(query)
+
+    def _update_query(self, keys, data):
+        query = dict()
+        equal = True
+        if self._is_rename():
+            new = self._term.get('name')
+            if data.name != new:
+                equal = False
+                query['name'] = new
+
+        return equal, query
+
+    def _update_requests(self, data):
+        updates = {
+            ('name', 'update'): self._update_requests_rename,
+            ('installer', 'add'): self._update_requests_add_installer,
+            ('installer', 'delete'): self._update_requests_delete_installer,
+            ('version', 'add'): self._update_requests_add_version,
+            ('version', 'delete'): self._update_requests_delete_version,
+            ('owner', 'update'): self._update_requests_change_owner,
+            ('project', 'add'): self._update_requests_add_project,
+            ('project', 'delete'): self._update_requests_delete_project,
+            ('customs', 'add'): self._update_requests_add_customs,
+            ('customs', 'delete'): self._update_requests_delete_customs,
+            ('score', 'add'): self._update_requests_add_score,
+            ('trust_indicator', 'add'): self._update_requests_add_ti,
+        }
+
+        updates[(self._field, self._op)](data)
+
+        return data.format()
+
+    def _iter_installers(xstep):
+        @functools.wraps(xstep)
+        def magic(self, data):
+            [xstep(self, installer)
+             for installer in self._filter_installers(data.installers)]
+        return magic
+
+    def _iter_versions(xstep):
+        @functools.wraps(xstep)
+        def magic(self, installer):
+            [xstep(self, version)
+             for version in (self._filter_versions(installer.versions))]
+        return magic
+
+    def _iter_projects(xstep):
+        @functools.wraps(xstep)
+        def magic(self, version):
+            [xstep(self, project)
+             for project in (self._filter_projects(version.projects))]
+        return magic
+
+    def _update_requests_rename(self, data):
+        data.name = self._term.get('name')
+        if not data.name:
+            raises.BadRequest(message.missing('name'))
+
+    def _update_requests_add_installer(self, data):
+        data.installers.append(models.ScenarioInstaller.from_dict(self._term))
+
+    def _update_requests_delete_installer(self, data):
+        data.installers = self._remove_installers(data.installers)
+
+    @_iter_installers
+    def _update_requests_add_version(self, installer):
+        installer.versions.append(models.ScenarioVersion.from_dict(self._term))
+
+    @_iter_installers
+    def _update_requests_delete_version(self, installer):
+        installer.versions = self._remove_versions(installer.versions)
+
+    @_iter_installers
+    @_iter_versions
+    def _update_requests_change_owner(self, version):
+        version.owner = self._term.get('owner')
+
+    @_iter_installers
+    @_iter_versions
+    def _update_requests_add_project(self, version):
+        version.projects.append(models.ScenarioProject.from_dict(self._term))
+
+    @_iter_installers
+    @_iter_versions
+    def _update_requests_delete_project(self, version):
+        version.projects = self._remove_projects(version.projects)
+
+    @_iter_installers
+    @_iter_versions
+    @_iter_projects
+    def _update_requests_add_customs(self, project):
+        project.customs = list(set(project.customs + self._term))
+
+    @_iter_installers
+    @_iter_versions
+    @_iter_projects
+    def _update_requests_delete_customs(self, project):
+        project.customs = filter(
+            lambda f: f not in self._term,
+            project.customs)
+
+    @_iter_installers
+    @_iter_versions
+    @_iter_projects
+    def _update_requests_add_score(self, project):
+        project.scores.append(
+            models.ScenarioScore.from_dict(self._term))
+
+    @_iter_installers
+    @_iter_versions
+    @_iter_projects
+    def _update_requests_add_ti(self, project):
+        project.trust_indicators.append(
+            models.ScenarioTI.from_dict(self._term))
+
+    def _is_rename(self):
+        return self._field == 'name' and self._op == 'update'
+
+    def _remove_installers(self, installers):
+        return self._remove('installer', installers)
+
+    def _filter_installers(self, installers):
+        return self._filter('installer', installers)
+
+    def _remove_versions(self, versions):
+        return self._remove('version', versions)
+
+    def _filter_versions(self, versions):
+        return self._filter('version', versions)
+
+    def _remove_projects(self, projects):
+        return self._remove('project', projects)
+
+    def _filter_projects(self, projects):
+        return self._filter('project', projects)
+
+    def _remove(self, field, fields):
+        return filter(
+            lambda f: getattr(f, field) != self._locate.get(field),
+            fields)
+
+    def _filter(self, field, fields):
+        return filter(
+            lambda f: getattr(f, field) == self._locate.get(field),
+            fields)
+
+    @property
+    def _field(self):
+        return self.json_args.get('field')
+
+    @property
+    def _op(self):
+        return self.json_args.get('op')
+
+    @property
+    def _locate(self):
+        return self.json_args.get('locate')
+
+    @property
+    def _term(self):
+        return self.json_args.get('term')
diff --git a/utils/test/testapi/opnfv_testapi/resources/scenario_models.py b/utils/test/testapi/opnfv_testapi/resources/scenario_models.py
new file mode 100644 (file)
index 0000000..b84accf
--- /dev/null
@@ -0,0 +1,204 @@
+import models
+from opnfv_testapi.tornado_swagger import swagger
+
+
+def list_default(value):
+    return value if value else list()
+
+
+def dict_default(value):
+    return value if value else dict()
+
+
+@swagger.model()
+class ScenarioTI(models.ModelBase):
+    def __init__(self, date=None, status='silver'):
+        self.date = date
+        self.status = status
+
+
+@swagger.model()
+class ScenarioScore(models.ModelBase):
+    def __init__(self, date=None, score='0'):
+        self.date = date
+        self.score = score
+
+
+@swagger.model()
+class ScenarioProject(models.ModelBase):
+    """
+        @property customs:
+        @ptype customs: C{list} of L{string}
+        @property scores:
+        @ptype scores: C{list} of L{ScenarioScore}
+        @property trust_indicators:
+        @ptype trust_indicators: C{list} of L{ScenarioTI}
+    """
+    def __init__(self,
+                 project='',
+                 customs=None,
+                 scores=None,
+                 trust_indicators=None):
+        self.project = project
+        self.customs = list_default(customs)
+        self.scores = list_default(scores)
+        self.trust_indicators = list_default(trust_indicators)
+
+    @staticmethod
+    def attr_parser():
+        return {'scores': ScenarioScore,
+                'trust_indicators': ScenarioTI}
+
+    def __eq__(self, other):
+        return [self.project == other.project and
+                self._customs_eq(other) and
+                self._scores_eq(other) and
+                self._ti_eq(other)]
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def _customs_eq(self, other):
+        return set(self.customs) == set(other.customs)
+
+    def _scores_eq(self, other):
+        return set(self.scores) == set(other.scores)
+
+    def _ti_eq(self, other):
+        return set(self.trust_indicators) == set(other.trust_indicators)
+
+
+@swagger.model()
+class ScenarioVersion(models.ModelBase):
+    """
+        @property projects:
+        @ptype projects: C{list} of L{ScenarioProject}
+    """
+    def __init__(self, version=None, projects=None):
+        self.version = version
+        self.projects = list_default(projects)
+
+    @staticmethod
+    def attr_parser():
+        return {'projects': ScenarioProject}
+
+    def __eq__(self, other):
+        return [self.version == other.version and self._projects_eq(other)]
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def _projects_eq(self, other):
+        for s_project in self.projects:
+            for o_project in other.projects:
+                if s_project.project == o_project.project:
+                    if s_project != o_project:
+                        return False
+
+        return True
+
+
+@swagger.model()
+class ScenarioInstaller(models.ModelBase):
+    """
+        @property versions:
+        @ptype versions: C{list} of L{ScenarioVersion}
+    """
+    def __init__(self, installer=None, versions=None):
+        self.installer = installer
+        self.versions = list_default(versions)
+
+    @staticmethod
+    def attr_parser():
+        return {'versions': ScenarioVersion}
+
+    def __eq__(self, other):
+        return [self.installer == other.installer and self._versions_eq(other)]
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def _versions_eq(self, other):
+        for s_version in self.versions:
+            for o_version in other.versions:
+                if s_version.version == o_version.version:
+                    if s_version != o_version:
+                        return False
+
+        return True
+
+
+@swagger.model()
+class ScenarioCreateRequest(models.ModelBase):
+    """
+        @property installers:
+        @ptype installers: C{list} of L{ScenarioInstaller}
+    """
+    def __init__(self, name='', installers=None):
+        self.name = name
+        self.installers = list_default(installers)
+
+    @staticmethod
+    def attr_parser():
+        return {'installers': ScenarioInstaller}
+
+
+@swagger.model()
+class ScenarioUpdateRequest(models.ModelBase):
+    """
+        @property field: update field
+        @property op: add/delete/update
+        @property locate: information used to locate the field
+        @property term: new value
+    """
+    def __init__(self, field=None, op=None, locate=None, term=None):
+        self.field = field
+        self.op = op
+        self.locate = dict_default(locate)
+        self.term = dict_default(term)
+
+
+@swagger.model()
+class Scenario(models.ModelBase):
+    """
+        @property installers:
+        @ptype installers: C{list} of L{ScenarioInstaller}
+    """
+    def __init__(self, name='', create_date='', _id='', installers=None):
+        self.name = name
+        self._id = _id
+        self.creation_date = create_date
+        self.installers = list_default(installers)
+
+    @staticmethod
+    def attr_parser():
+        return {'installers': ScenarioInstaller}
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __eq__(self, other):
+        return [self.name == other.name and self._installers_eq(other)]
+
+    def _installers_eq(self, other):
+        for s_install in self.installers:
+            for o_install in other.installers:
+                if s_install.installer == o_install.installer:
+                    if s_install != o_install:
+                        return False
+
+        return True
+
+
+@swagger.model()
+class Scenarios(models.ModelBase):
+    """
+        @property scenarios:
+        @ptype scenarios: C{list} of L{Scenario}
+    """
+    def __init__(self):
+        self.scenarios = list()
+
+    @staticmethod
+    def attr_parser():
+        return {'scenarios': Scenario}
index 253aa66..bc22b74 100644 (file)
@@ -6,23 +6,25 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-from opnfv_testapi.common.constants import HTTP_FORBIDDEN
-from opnfv_testapi.resources.handlers import GenericApiHandler
-from opnfv_testapi.resources.testcase_models import Testcase
+import httplib
+
+from opnfv_testapi.common import message
+from opnfv_testapi.resources import handlers
+from opnfv_testapi.resources import testcase_models
 from opnfv_testapi.tornado_swagger import swagger
 
 
-class GenericTestcaseHandler(GenericApiHandler):
+class GenericTestcaseHandler(handlers.GenericApiHandler):
     def __init__(self, application, request, **kwargs):
         super(GenericTestcaseHandler, self).__init__(application,
                                                      request,
                                                      **kwargs)
         self.table = self.db_testcases
-        self.table_cls = Testcase
+        self.table_cls = testcase_models.Testcase
 
 
 class TestcaseCLHandler(GenericTestcaseHandler):
-    @swagger.operation(nickname="list-all")
+    @swagger.operation(nickname="listAllTestCases")
     def get(self, project_name):
         """
             @description: list all testcases of a project by project_name
@@ -34,7 +36,7 @@ class TestcaseCLHandler(GenericTestcaseHandler):
         query['project_name'] = project_name
         self._list(query)
 
-    @swagger.operation(nickname="create")
+    @swagger.operation(nickname="createTestCase")
     def post(self, project_name):
         """
             @description: create a testcase of a project by project_name
@@ -57,13 +59,11 @@ class TestcaseCLHandler(GenericTestcaseHandler):
             }
 
         def p_error(data):
-            message = 'Could not find project [{}]'.format(data.project_name)
-            return HTTP_FORBIDDEN, message
+            return httplib.FORBIDDEN, message.not_found('project',
+                                                        data.project_name)
 
         def tc_error(data):
-            message = '{} already exists as a testcase in project {}'\
-                .format(data.name, data.project_name)
-            return HTTP_FORBIDDEN, message
+            return httplib.FORBIDDEN, message.exist('testcase', data.name)
 
         miss_checks = ['name']
         db_checks = [(self.db_projects, True, p_query, p_error),
@@ -72,7 +72,7 @@ class TestcaseCLHandler(GenericTestcaseHandler):
 
 
 class TestcaseGURHandler(GenericTestcaseHandler):
-    @swagger.operation(nickname='get-one')
+    @swagger.operation(nickname='getTestCaseByName')
     def get(self, project_name, case_name):
         """
             @description: get a single testcase
@@ -86,7 +86,7 @@ class TestcaseGURHandler(GenericTestcaseHandler):
         query["name"] = case_name
         self._get_one(query)
 
-    @swagger.operation(nickname="update")
+    @swagger.operation(nickname="updateTestCaseByName")
     def put(self, project_name, case_name):
         """
             @description: update a single testcase
@@ -104,7 +104,7 @@ class TestcaseGURHandler(GenericTestcaseHandler):
         db_keys = ['name', 'project_name']
         self._update(query, db_keys)
 
-    @swagger.operation(nickname='delete')
+    @swagger.operation(nickname='deleteTestCaseByName')
     def delete(self, project_name, case_name):
         """
             @description: delete a testcase by project_name and case_name
index c9dce60..8cc3c6c 100644 (file)
@@ -6,87 +6,80 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import models
 from opnfv_testapi.tornado_swagger import swagger
 
 
 @swagger.model()
-class TestcaseCreateRequest(object):
-    def __init__(self, name, url=None, description=None):
+class TestcaseCreateRequest(models.ModelBase):
+    def __init__(self, name, url=None, description=None,
+                 tier=None, ci_loop=None, criteria=None,
+                 blocking=None, dependencies=None, run=None,
+                 domains=None, tags=None, version=None):
         self.name = name
         self.url = url
         self.description = description
-
-    def format(self):
-        return {
-            "name": self.name,
-            "description": self.description,
-            "url": self.url,
-        }
+        self.tier = tier
+        self.ci_loop = ci_loop
+        self.criteria = criteria
+        self.blocking = blocking
+        self.dependencies = dependencies
+        self.run = run
+        self.domains = domains
+        self.tags = tags
+        self.version = version
+        self.trust = "Silver"
 
 
 @swagger.model()
-class TestcaseUpdateRequest(object):
-    def __init__(self, name=None, description=None, project_name=None):
+class TestcaseUpdateRequest(models.ModelBase):
+    def __init__(self, name=None, description=None, project_name=None,
+                 tier=None, ci_loop=None, criteria=None,
+                 blocking=None, dependencies=None, run=None,
+                 domains=None, tags=None, version=None, trust=None):
         self.name = name
         self.description = description
         self.project_name = project_name
-
-    def format(self):
-        return {
-            "name": self.name,
-            "description": self.description,
-            "project_name": self.project_name,
-        }
+        self.tier = tier
+        self.ci_loop = ci_loop
+        self.criteria = criteria
+        self.blocking = blocking
+        self.dependencies = dependencies
+        self.run = run
+        self.domains = domains
+        self.tags = tags
+        self.version = version
+        self.trust = trust
 
 
 @swagger.model()
-class Testcase(object):
-    def __init__(self):
+class Testcase(models.ModelBase):
+    def __init__(self, _id=None, name=None, project_name=None,
+                 description=None, url=None, creation_date=None,
+                 tier=None, ci_loop=None, criteria=None,
+                 blocking=None, dependencies=None, run=None,
+                 domains=None, tags=None, version=None,
+                 trust=None):
         self._id = None
         self.name = None
         self.project_name = None
         self.description = None
         self.url = None
         self.creation_date = None
-
-    @staticmethod
-    def from_dict(a_dict):
-
-        if a_dict is None:
-            return None
-
-        t = Testcase()
-        t._id = a_dict.get('_id')
-        t.project_name = a_dict.get('project_name')
-        t.creation_date = a_dict.get('creation_date')
-        t.name = a_dict.get('name')
-        t.description = a_dict.get('description')
-        t.url = a_dict.get('url')
-
-        return t
-
-    def format(self):
-        return {
-            "name": self.name,
-            "description": self.description,
-            "project_name": self.project_name,
-            "creation_date": str(self.creation_date),
-            "url": self.url
-        }
-
-    def format_http(self):
-        return {
-            "_id": str(self._id),
-            "name": self.name,
-            "project_name": self.project_name,
-            "description": self.description,
-            "creation_date": str(self.creation_date),
-            "url": self.url,
-        }
+        self.tier = None
+        self.ci_loop = None
+        self.criteria = None
+        self.blocking = None
+        self.dependencies = None
+        self.run = None
+        self.domains = None
+        self.tags = None
+        self.version = None
+        self.trust = None
 
 
 @swagger.model()
-class Testcases(object):
+class Testcases(models.ModelBase):
     """
         @property testcases:
         @ptype testcases: C{list} of L{Testcase}
@@ -95,11 +88,5 @@ class Testcases(object):
         self.testcases = list()
 
     @staticmethod
-    def from_dict(res_dict):
-        if res_dict is None:
-            return None
-
-        res = Testcases()
-        for testcase in res_dict.get('testcases'):
-            res.testcases.append(Testcase.from_dict(testcase))
-        return res
+    def attr_parser():
+        return {'testcases': Testcase}
index eb648ec..39cf006 100644 (file)
@@ -6,36 +6,34 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-from opnfv_testapi.resources.handlers import VersionHandler
-from opnfv_testapi.resources.testcase_handlers import TestcaseCLHandler, \
-    TestcaseGURHandler
-from opnfv_testapi.resources.pod_handlers import PodCLHandler, PodGURHandler
-from opnfv_testapi.resources.project_handlers import ProjectCLHandler, \
-    ProjectGURHandler
-from opnfv_testapi.resources.result_handlers import ResultsCLHandler, \
-    ResultsGURHandler
-
+from opnfv_testapi.resources import handlers
+from opnfv_testapi.resources import pod_handlers
+from opnfv_testapi.resources import project_handlers
+from opnfv_testapi.resources import result_handlers
+from opnfv_testapi.resources import scenario_handlers
+from opnfv_testapi.resources import testcase_handlers
 
 mappings = [
     # GET /versions => GET API version
-    (r"/versions", VersionHandler),
+    (r"/versions", handlers.VersionHandler),
 
     # few examples:
     # GET /api/v1/pods => Get all pods
     # GET /api/v1/pods/1 => Get details on POD 1
-    (r"/api/v1/pods", PodCLHandler),
-    (r"/api/v1/pods/([^/]+)", PodGURHandler),
+    (r"/api/v1/pods", pod_handlers.PodCLHandler),
+    (r"/api/v1/pods/([^/]+)", pod_handlers.PodGURHandler),
 
     # few examples:
     # GET /projects
     # GET /projects/yardstick
-    (r"/api/v1/projects", ProjectCLHandler),
-    (r"/api/v1/projects/([^/]+)", ProjectGURHandler),
+    (r"/api/v1/projects", project_handlers.ProjectCLHandler),
+    (r"/api/v1/projects/([^/]+)", project_handlers.ProjectGURHandler),
 
     # few examples
     # GET /projects/qtip/cases => Get cases for qtip
-    (r"/api/v1/projects/([^/]+)/cases", TestcaseCLHandler),
-    (r"/api/v1/projects/([^/]+)/cases/([^/]+)", TestcaseGURHandler),
+    (r"/api/v1/projects/([^/]+)/cases", testcase_handlers.TestcaseCLHandler),
+    (r"/api/v1/projects/([^/]+)/cases/([^/]+)",
+     testcase_handlers.TestcaseGURHandler),
 
     # new path to avoid a long depth
     # GET /results?project=functest&case=keystone.catalog&pod=1
@@ -43,6 +41,10 @@ mappings = [
     # POST /results =>
     # Push results with mandatory request payload parameters
     # (project, case, and pod)
-    (r"/api/v1/results", ResultsCLHandler),
-    (r"/api/v1/results/([^/]+)", ResultsGURHandler),
+    (r"/api/v1/results", result_handlers.ResultsCLHandler),
+    (r"/api/v1/results/([^/]+)", result_handlers.ResultsGURHandler),
+
+    # scenarios
+    (r"/api/v1/scenarios", scenario_handlers.ScenariosCLHandler),
+    (r"/api/v1/scenarios/([^/]+)", scenario_handlers.ScenarioGURHandler),
 ]
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/__init__.py b/utils/test/testapi/opnfv_testapi/tests/unit/common/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/noparam.ini b/utils/test/testapi/opnfv_testapi/tests/unit/common/noparam.ini
new file mode 100644 (file)
index 0000000..fda2a09
--- /dev/null
@@ -0,0 +1,16 @@
+# to add a new parameter in the config file,
+# the CONF object in config.ini must be updated
+[mongo]
+# URL of the mongo DB
+# Mongo auth url => mongodb://user1:pwd1@host1/?authSource=db1
+url = mongodb://127.0.0.1:27017/
+
+[api]
+# Listening port
+port = 8000
+# With debug_on set to true, error traces will be shown in HTTP responses
+debug = True
+authenticate = False
+
+[swagger]
+base_url = http://localhost:8000
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/normal.ini b/utils/test/testapi/opnfv_testapi/tests/unit/common/normal.ini
new file mode 100644 (file)
index 0000000..77cc6c6
--- /dev/null
@@ -0,0 +1,17 @@
+# to add a new parameter in the config file,
+# the CONF object in config.ini must be updated
+[mongo]
+# URL of the mongo DB
+# Mongo auth url => mongodb://user1:pwd1@host1/?authSource=db1
+url = mongodb://127.0.0.1:27017/
+dbname = test_results_collection
+
+[api]
+# Listening port
+port = 8000
+# With debug_on set to true, error traces will be shown in HTTP responses
+debug = True
+authenticate = False
+
+[swagger]
+base_url = http://localhost:8000
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/nosection.ini b/utils/test/testapi/opnfv_testapi/tests/unit/common/nosection.ini
new file mode 100644 (file)
index 0000000..9988fc0
--- /dev/null
@@ -0,0 +1,11 @@
+# to add a new parameter in the config file,
+# the CONF object in config.ini must be updated
+[api]
+# Listening port
+port = 8000
+# With debug_on set to true, error traces will be shown in HTTP responses
+debug = True
+authenticate = False
+
+[swagger]
+base_url = http://localhost:8000
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/notboolean.ini b/utils/test/testapi/opnfv_testapi/tests/unit/common/notboolean.ini
new file mode 100644 (file)
index 0000000..b3f3276
--- /dev/null
@@ -0,0 +1,17 @@
+# to add a new parameter in the config file,
+# the CONF object in config.ini must be updated
+[mongo]
+# URL of the mongo DB
+# Mongo auth url => mongodb://user1:pwd1@host1/?authSource=db1
+url = mongodb://127.0.0.1:27017/
+dbname = test_results_collection
+
+[api]
+# Listening port
+port = 8000
+# With debug_on set to true, error traces will be shown in HTTP responses
+debug = True
+authenticate = notboolean
+
+[swagger]
+base_url = http://localhost:8000
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/notint.ini b/utils/test/testapi/opnfv_testapi/tests/unit/common/notint.ini
new file mode 100644 (file)
index 0000000..d1b752a
--- /dev/null
@@ -0,0 +1,17 @@
+# to add a new parameter in the config file,
+# the CONF object in config.ini must be updated
+[mongo]
+# URL of the mongo DB
+# Mongo auth url => mongodb://user1:pwd1@host1/?authSource=db1
+url = mongodb://127.0.0.1:27017/
+dbname = test_results_collection
+
+[api]
+# Listening port
+port = notint
+# With debug_on set to true, error traces will be shown in HTTP responses
+debug = True
+authenticate = False
+
+[swagger]
+base_url = http://localhost:8000
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/common/test_config.py b/utils/test/testapi/opnfv_testapi/tests/unit/common/test_config.py
new file mode 100644 (file)
index 0000000..aaff6bb
--- /dev/null
@@ -0,0 +1,36 @@
+import ConfigParser
+import os
+
+import pytest
+
+from opnfv_testapi.common import config
+
+
+@pytest.fixture()
+def config_dir():
+    return os.path.dirname(__file__)
+
+
+@pytest.mark.parametrize('exception, config_file, excepted', [
+    (config.ParseError, None, '/etc/opnfv_testapi/config.ini not found'),
+    (ConfigParser.NoSectionError, 'nosection.ini', 'No section:'),
+    (config.ParseError, 'noparam.ini', 'No parameter:'),
+    (config.ParseError, 'notint.ini', 'Not int:'),
+    (config.ParseError, 'notboolean.ini', 'Not boolean:')])
+def pytest_config_exceptions(config_dir, exception, config_file, excepted):
+    file = '{}/{}'.format(config_dir, config_file) if config_file else None
+    with pytest.raises(exception) as error:
+        config.APIConfig().parse(file)
+    assert excepted in str(error.value)
+
+
+def test_config_success():
+    config_dir = os.path.join(os.path.dirname(__file__),
+                              '../../../../etc/config.ini')
+    conf = config.APIConfig().parse(config_dir)
+    assert conf.mongo_url == 'mongodb://127.0.0.1:27017/'
+    assert conf.mongo_dbname == 'test_results_collection'
+    assert conf.api_port == 8000
+    assert conf.api_debug_on is True
+    assert conf.api_authenticate_on is False
+    assert conf.swagger_base_url == 'http://localhost:8000'
index 3dd87e6..ef74a08 100644 (file)
@@ -55,7 +55,8 @@ class MemCursor(object):
 
 class MemDb(object):
 
-    def __init__(self):
+    def __init__(self, name):
+        self.name = name
         self.contents = []
         pass
 
@@ -109,8 +110,59 @@ class MemDb(object):
                 return True
         return False
 
-    @staticmethod
-    def _in(content, *args):
+    def _in(self, content, *args):
+        if self.name == 'scenarios':
+            return self._in_scenarios(content, *args)
+        else:
+            return self._in_others(content, *args)
+
+    def _in_scenarios_installer(self, installer, content):
+        hit = False
+        for s_installer in content['installers']:
+            if installer == s_installer['installer']:
+                hit = True
+
+        return hit
+
+    def _in_scenarios_version(self, version, content):
+        hit = False
+        for s_installer in content['installers']:
+            for s_version in s_installer['versions']:
+                if version == s_version['version']:
+                    hit = True
+        return hit
+
+    def _in_scenarios_project(self, project, content):
+        hit = False
+        for s_installer in content['installers']:
+            for s_version in s_installer['versions']:
+                for s_project in s_version['projects']:
+                    if project == s_project['project']:
+                        hit = True
+
+        return hit
+
+    def _in_scenarios(self, content, *args):
+        for arg in args:
+            for k, v in arg.iteritems():
+                if k == 'installers':
+                    for inner in v.values():
+                        for i_k, i_v in inner.iteritems():
+                            if i_k == 'installer':
+                                return self._in_scenarios_installer(i_v,
+                                                                    content)
+                            elif i_k == 'versions.version':
+                                return self._in_scenarios_version(i_v,
+                                                                  content)
+                            elif i_k == 'versions.projects.project':
+                                return self._in_scenarios_project(i_v,
+                                                                  content)
+                elif content.get(k, None) != v:
+                    return False
+
+        return True
+
+    def _in_others(self, content, *args):
         for arg in args:
             for k, v in arg.iteritems():
                 if k == 'start_date':
@@ -185,7 +237,9 @@ def __getattr__(name):
     return globals()[name]
 
 
-pods = MemDb()
-projects = MemDb()
-testcases = MemDb()
-results = MemDb()
+pods = MemDb('pods')
+projects = MemDb('projects')
+testcases = MemDb('testcases')
+results = MemDb('results')
+scenarios = MemDb('scenarios')
+tokens = MemDb('tokens')
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/scenario-c1.json b/utils/test/testapi/opnfv_testapi/tests/unit/scenario-c1.json
new file mode 100644 (file)
index 0000000..1878022
--- /dev/null
@@ -0,0 +1,38 @@
+{
+  "name": "nosdn-nofeature-ha",
+  "installers":
+  [
+    {
+      "installer": "apex",
+      "versions":
+      [
+        {
+          "owner": "Luke",
+          "version": "master",
+          "projects":
+          [
+            {
+              "project": "functest",
+              "customs": [ "healthcheck", "vping_ssh"],
+              "scores":
+              [
+                {
+                  "date": "2017-01-08 22:46:44",
+                  "score": "12/14"
+                }
+
+              ],
+              "trust_indicators": []
+            },
+            {
+              "project": "yardstick",
+              "customs": [],
+              "scores": [],
+              "trust_indicators": []
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/scenario-c2.json b/utils/test/testapi/opnfv_testapi/tests/unit/scenario-c2.json
new file mode 100644 (file)
index 0000000..b6a3b83
--- /dev/null
@@ -0,0 +1,73 @@
+{
+  "name": "odl_2-nofeature-ha",
+  "installers":
+  [
+    {
+      "installer": "fuel",
+      "versions":
+      [
+        {
+          "owner": "Lucky",
+          "version": "colorado",
+          "projects":
+          [
+            {
+              "project": "functest",
+              "customs": [ "healthcheck", "vping_ssh"],
+              "scores": [],
+              "trust_indicators": [
+                {
+                  "date": "2017-01-18 22:46:44",
+                  "status": "silver"
+                }
+
+              ]
+            },
+            {
+              "project": "yardstick",
+              "customs": ["suite-a"],
+              "scores": [
+                {
+                  "date": "2017-01-08 22:46:44",
+                  "score": "0"
+                }
+              ],
+              "trust_indicators": [
+                {
+                  "date": "2017-01-18 22:46:44",
+                  "status": "gold"
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "owner": "Luke",
+          "version": "colorado",
+          "projects":
+          [
+            {
+              "project": "functest",
+              "customs": [ "healthcheck", "vping_ssh"],
+              "scores":
+              [
+                {
+                  "date": "2017-01-09 22:46:44",
+                  "score": "11/14"
+                }
+
+              ],
+              "trust_indicators": []
+            },
+            {
+              "project": "yardstick",
+              "customs": [],
+              "scores": [],
+              "trust_indicators": []
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
index ff1a193..b955f4a 100644 (file)
@@ -7,21 +7,23 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 import json
+from os import path
 
-from tornado.web import Application
-from tornado.testing import AsyncHTTPTestCase
+import mock
+from tornado import testing
 
-from opnfv_testapi.router import url_mappings
-from opnfv_testapi.resources.models import CreateResponse
 import fake_pymongo
+from opnfv_testapi.cmd import server
+from opnfv_testapi.resources import models
 
 
-class TestBase(AsyncHTTPTestCase):
+class TestBase(testing.AsyncHTTPTestCase):
     headers = {'Content-Type': 'application/json; charset=UTF-8'}
 
     def setUp(self):
+        self._patch_server()
         self.basePath = ''
-        self.create_res = CreateResponse
+        self.create_res = models.CreateResponse
         self.get_res = None
         self.list_res = None
         self.update_res = None
@@ -30,12 +32,24 @@ class TestBase(AsyncHTTPTestCase):
         self.addCleanup(self._clear)
         super(TestBase, self).setUp()
 
+    def tearDown(self):
+        self.db_patcher.stop()
+
+    def _patch_server(self):
+        server.parse_config([
+            '--config-file',
+            path.join(path.dirname(__file__), 'common/normal.ini')
+        ])
+        self.db_patcher = mock.patch('opnfv_testapi.cmd.server.get_db',
+                                     self._fake_pymongo)
+        self.db_patcher.start()
+
+    @staticmethod
+    def _fake_pymongo():
+        return fake_pymongo
+
     def get_app(self):
-        return Application(
-            url_mappings.mappings,
-            db=fake_pymongo,
-            debug=True,
-        )
+        return server.make_app()
 
     def create_d(self, *args):
         return self.create(self.req_d, *args)
@@ -47,7 +61,7 @@ class TestBase(AsyncHTTPTestCase):
         return self.create_help(self.basePath, req, *args)
 
     def create_help(self, uri, req, *args):
-        if req:
+        if req and not isinstance(req, str) and hasattr(req, 'format'):
             req = req.format()
         res = self.fetch(self._update_uri(uri, *args),
                          method='POST',
@@ -97,7 +111,7 @@ class TestBase(AsyncHTTPTestCase):
         return uri.count('%s')
 
     def _get_query_uri(self, query):
-        return self.basePath + '?' + query
+        return self.basePath + '?' + query if query else self.basePath
 
     def _get_uri(self, *args):
         return self._update_uri(self.basePath, *args)
@@ -123,9 +137,17 @@ class TestBase(AsyncHTTPTestCase):
         self.assertIn(self.basePath, body.href)
 
     def assert_create_body(self, body, req=None, *args):
+        import inspect
         if not req:
             req = self.req_d
-        new_args = args + tuple([req.name])
+        resource_name = ''
+        if inspect.isclass(req):
+            resource_name = req.name
+        elif isinstance(req, dict):
+            resource_name = req['name']
+        elif isinstance(req, str):
+            resource_name = json.loads(req)['name']
+        new_args = args + tuple([resource_name])
         self.assertIn(self._get_uri(*new_args), body.href)
 
     @staticmethod
@@ -134,3 +156,4 @@ class TestBase(AsyncHTTPTestCase):
         fake_pymongo.projects.clear()
         fake_pymongo.testcases.clear()
         fake_pymongo.results.clear()
+        fake_pymongo.scenarios.clear()
index 5f50ba8..7c43fca 100644 (file)
@@ -9,13 +9,13 @@
 import unittest
 
 from tornado import gen
-from tornado.testing import AsyncHTTPTestCase, gen_test
-from tornado.web import Application
+from tornado import testing
+from tornado import web
 
 import fake_pymongo
 
 
-class MyTest(AsyncHTTPTestCase):
+class MyTest(testing.AsyncHTTPTestCase):
     def setUp(self):
         super(MyTest, self).setUp()
         self.db = fake_pymongo
@@ -23,7 +23,7 @@ class MyTest(AsyncHTTPTestCase):
         self.io_loop.run_sync(self.fixture_setup)
 
     def get_app(self):
-        return Application()
+        return web.Application()
 
     @gen.coroutine
     def fixture_setup(self):
@@ -32,13 +32,13 @@ class MyTest(AsyncHTTPTestCase):
         yield self.db.pods.insert({'_id': '1', 'name': 'test1'})
         yield self.db.pods.insert({'name': 'test2'})
 
-    @gen_test
+    @testing.gen_test
     def test_find_one(self):
         user = yield self.db.pods.find_one({'name': 'test1'})
         self.assertEqual(user, self.test1)
         self.db.pods.remove()
 
-    @gen_test
+    @testing.gen_test
     def test_find(self):
         cursor = self.db.pods.find()
         names = []
@@ -47,7 +47,7 @@ class MyTest(AsyncHTTPTestCase):
             names.append(ob.get('name'))
         self.assertItemsEqual(names, ['test1', 'test2'])
 
-    @gen_test
+    @testing.gen_test
     def test_update(self):
         yield self.db.pods.update({'_id': '1'}, {'name': 'new_test1'})
         user = yield self.db.pods.find_one({'_id': '1'})
@@ -71,7 +71,7 @@ class MyTest(AsyncHTTPTestCase):
                             None,
                             check_keys=False)
 
-    @gen_test
+    @testing.gen_test
     def test_remove(self):
         yield self.db.pods.remove({'_id': '1'})
         user = yield self.db.pods.find_one({'_id': '1'})
@@ -104,7 +104,7 @@ class MyTest(AsyncHTTPTestCase):
     def _insert_assert(self, docs, error=None, **kwargs):
         self._db_assert('insert', error, docs, **kwargs)
 
-    @gen_test
+    @testing.gen_test
     def _db_assert(self, method, error, *args, **kwargs):
         name_error = None
         try:
index a1184d5..cae86e8 100644 (file)
@@ -6,22 +6,22 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import httplib
 import unittest
 
-from test_base import TestBase
-from opnfv_testapi.resources.pod_models import PodCreateRequest, Pod, Pods
-from opnfv_testapi.common.constants import HTTP_OK, HTTP_BAD_REQUEST, \
-    HTTP_FORBIDDEN, HTTP_NOT_FOUND
+from opnfv_testapi.common import message
+from opnfv_testapi.resources import pod_models
+import test_base as base
 
 
-class TestPodBase(TestBase):
+class TestPodBase(base.TestBase):
     def setUp(self):
         super(TestPodBase, self).setUp()
-        self.req_d = PodCreateRequest('zte-1', 'virtual',
-                                      'zte pod 1', 'ci-pod')
-        self.req_e = PodCreateRequest('zte-2', 'metal', 'zte pod 2')
-        self.get_res = Pod
-        self.list_res = Pods
+        self.req_d = pod_models.PodCreateRequest('zte-1', 'virtual',
+                                                 'zte pod 1', 'ci-pod')
+        self.req_e = pod_models.PodCreateRequest('zte-2', 'metal', 'zte pod 2')
+        self.get_res = pod_models.Pod
+        self.list_res = pod_models.Pods
         self.basePath = '/api/v1/pods'
 
     def assert_get_body(self, pod, req=None):
@@ -38,36 +38,36 @@ class TestPodBase(TestBase):
 class TestPodCreate(TestPodBase):
     def test_withoutBody(self):
         (code, body) = self.create()
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
 
     def test_emptyName(self):
-        req_empty = PodCreateRequest('')
+        req_empty = pod_models.PodCreateRequest('')
         (code, body) = self.create(req_empty)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
 
     def test_noneName(self):
-        req_none = PodCreateRequest(None)
+        req_none = pod_models.PodCreateRequest(None)
         (code, body) = self.create(req_none)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
 
     def test_success(self):
         code, body = self.create_d()
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_create_body(body)
 
     def test_alreadyExist(self):
         self.create_d()
         code, body = self.create_d()
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn('already exists', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.exist_base, body)
 
 
 class TestPodGet(TestPodBase):
     def test_notExist(self):
         code, body = self.get('notExist')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_getOne(self):
         self.create_d()
index 327ddf7..74cefd7 100644 (file)
@@ -6,23 +6,24 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
+import httplib
 import unittest
 
-from test_base import TestBase
-from opnfv_testapi.resources.project_models import ProjectCreateRequest, \
-    Project, Projects, ProjectUpdateRequest
-from opnfv_testapi.common.constants import HTTP_OK, HTTP_BAD_REQUEST, \
-    HTTP_FORBIDDEN, HTTP_NOT_FOUND
+from opnfv_testapi.common import message
+from opnfv_testapi.resources import project_models
+import test_base as base
 
 
-class TestProjectBase(TestBase):
+class TestProjectBase(base.TestBase):
     def setUp(self):
         super(TestProjectBase, self).setUp()
-        self.req_d = ProjectCreateRequest('vping', 'vping-ssh test')
-        self.req_e = ProjectCreateRequest('doctor', 'doctor test')
-        self.get_res = Project
-        self.list_res = Projects
-        self.update_res = Project
+        self.req_d = project_models.ProjectCreateRequest('vping',
+                                                         'vping-ssh test')
+        self.req_e = project_models.ProjectCreateRequest('doctor',
+                                                         'doctor test')
+        self.get_res = project_models.Project
+        self.list_res = project_models.Projects
+        self.update_res = project_models.Project
         self.basePath = '/api/v1/projects'
 
     def assert_body(self, project, req=None):
@@ -37,41 +38,41 @@ class TestProjectBase(TestBase):
 class TestProjectCreate(TestProjectBase):
     def test_withoutBody(self):
         (code, body) = self.create()
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
 
     def test_emptyName(self):
-        req_empty = ProjectCreateRequest('')
+        req_empty = project_models.ProjectCreateRequest('')
         (code, body) = self.create(req_empty)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
 
     def test_noneName(self):
-        req_none = ProjectCreateRequest(None)
+        req_none = project_models.ProjectCreateRequest(None)
         (code, body) = self.create(req_none)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
 
     def test_success(self):
         (code, body) = self.create_d()
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_create_body(body)
 
     def test_alreadyExist(self):
         self.create_d()
         (code, body) = self.create_d()
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn('already exists', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.exist_base, body)
 
 
 class TestProjectGet(TestProjectBase):
     def test_notExist(self):
         code, body = self.get('notExist')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_getOne(self):
         self.create_d()
         code, body = self.get(self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_body(body)
 
     def test_list(self):
@@ -88,33 +89,33 @@ class TestProjectGet(TestProjectBase):
 class TestProjectUpdate(TestProjectBase):
     def test_withoutBody(self):
         code, _ = self.update(None, 'noBody')
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
 
     def test_notFound(self):
         code, _ = self.update(self.req_e, 'notFound')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_newNameExist(self):
         self.create_d()
         self.create_e()
         code, body = self.update(self.req_e, self.req_d.name)
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn("already exists", body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.exist_base, body)
 
     def test_noUpdate(self):
         self.create_d()
         code, body = self.update(self.req_d, self.req_d.name)
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn("Nothing to update", body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.no_update(), body)
 
     def test_success(self):
         self.create_d()
         code, body = self.get(self.req_d.name)
         _id = body._id
 
-        req = ProjectUpdateRequest('newName', 'new description')
+        req = project_models.ProjectUpdateRequest('newName', 'new description')
         code, body = self.update(req, self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assertEqual(_id, body._id)
         self.assert_body(body, req)
 
@@ -126,16 +127,16 @@ class TestProjectUpdate(TestProjectBase):
 class TestProjectDelete(TestProjectBase):
     def test_notFound(self):
         code, body = self.delete('notFound')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_success(self):
         self.create_d()
         code, body = self.delete(self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assertEqual(body, '')
 
         code, body = self.get(self.req_d.name)
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
 if __name__ == '__main__':
     unittest.main()
index 8479b35..2e0aa36 100644 (file)
@@ -7,17 +7,16 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 import copy
-import unittest
 from datetime import datetime, timedelta
+import httplib
+import unittest
 
-from opnfv_testapi.common.constants import HTTP_OK, HTTP_BAD_REQUEST, \
-    HTTP_NOT_FOUND
-from opnfv_testapi.resources.pod_models import PodCreateRequest
-from opnfv_testapi.resources.project_models import ProjectCreateRequest
-from opnfv_testapi.resources.result_models import ResultCreateRequest, \
-    TestResult, TestResults, ResultUpdateRequest, TI, TIHistory
-from opnfv_testapi.resources.testcase_models import TestcaseCreateRequest
-from test_base import TestBase
+from opnfv_testapi.common import message
+from opnfv_testapi.resources import pod_models
+from opnfv_testapi.resources import project_models
+from opnfv_testapi.resources import result_models
+from opnfv_testapi.resources import testcase_models
+import test_base as base
 
 
 class Details(object):
@@ -25,12 +24,14 @@ class Details(object):
         self.timestart = timestart
         self.duration = duration
         self.status = status
+        self.items = [{'item1': 1}, {'item2': 2}]
 
     def format(self):
         return {
             "timestart": self.timestart,
             "duration": self.duration,
-            "status": self.status
+            "status": self.status,
+            'items': [{'item1': 1}, {'item2': 2}]
         }
 
     @staticmethod
@@ -43,10 +44,11 @@ class Details(object):
         t.timestart = a_dict.get('timestart')
         t.duration = a_dict.get('duration')
         t.status = a_dict.get('status')
+        t.items = a_dict.get('items')
         return t
 
 
-class TestResultBase(TestBase):
+class TestResultBase(base.TestBase):
     def setUp(self):
         self.pod = 'zte-pod1'
         self.project = 'functest'
@@ -56,34 +58,41 @@ class TestResultBase(TestBase):
         self.build_tag = 'v3.0'
         self.scenario = 'odl-l2'
         self.criteria = 'passed'
-        self.trust_indicator = TI(0.7)
+        self.trust_indicator = result_models.TI(0.7)
         self.start_date = "2016-05-23 07:16:09.477097"
         self.stop_date = "2016-05-23 07:16:19.477097"
         self.update_date = "2016-05-24 07:16:19.477097"
         self.update_step = -0.05
         super(TestResultBase, self).setUp()
         self.details = Details(timestart='0', duration='9s', status='OK')
-        self.req_d = ResultCreateRequest(pod_name=self.pod,
-                                         project_name=self.project,
-                                         case_name=self.case,
-                                         installer=self.installer,
-                                         version=self.version,
-                                         start_date=self.start_date,
-                                         stop_date=self.stop_date,
-                                         details=self.details.format(),
-                                         build_tag=self.build_tag,
-                                         scenario=self.scenario,
-                                         criteria=self.criteria,
-                                         trust_indicator=self.trust_indicator)
-        self.get_res = TestResult
-        self.list_res = TestResults
-        self.update_res = TestResult
+        self.req_d = result_models.ResultCreateRequest(
+            pod_name=self.pod,
+            project_name=self.project,
+            case_name=self.case,
+            installer=self.installer,
+            version=self.version,
+            start_date=self.start_date,
+            stop_date=self.stop_date,
+            details=self.details.format(),
+            build_tag=self.build_tag,
+            scenario=self.scenario,
+            criteria=self.criteria,
+            trust_indicator=self.trust_indicator)
+        self.get_res = result_models.TestResult
+        self.list_res = result_models.TestResults
+        self.update_res = result_models.TestResult
         self.basePath = '/api/v1/results'
-        self.req_pod = PodCreateRequest(self.pod, 'metal', 'zte pod 1')
-        self.req_project = ProjectCreateRequest(self.project, 'vping test')
-        self.req_testcase = TestcaseCreateRequest(self.case,
-                                                  '/cases/vping',
-                                                  'vping-ssh test')
+        self.req_pod = pod_models.PodCreateRequest(
+            self.pod,
+            'metal',
+            'zte pod 1')
+        self.req_project = project_models.ProjectCreateRequest(
+            self.project,
+            'vping test')
+        self.req_testcase = testcase_models.TestcaseCreateRequest(
+            self.case,
+            '/cases/vping',
+            'vping-ssh test')
         self.create_help('/api/v1/pods', self.req_pod)
         self.create_help('/api/v1/projects', self.req_project)
         self.create_help('/api/v1/projects/%s/cases',
@@ -91,7 +100,7 @@ class TestResultBase(TestBase):
                          self.project)
 
     def assert_res(self, code, result, req=None):
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         if req is None:
             req = self.req_d
         self.assertEqual(result.pod_name, req.pod_name)
@@ -104,6 +113,7 @@ class TestResultBase(TestBase):
         self.assertEqual(details_res.duration, details_req.duration)
         self.assertEqual(details_res.timestart, details_req.timestart)
         self.assertEqual(details_res.status, details_req.status)
+        self.assertEqual(details_res.items, details_req.items)
         self.assertEqual(result.build_tag, req.build_tag)
         self.assertEqual(result.scenario, req.scenario)
         self.assertEqual(result.criteria, req.criteria)
@@ -125,78 +135,78 @@ class TestResultBase(TestBase):
 class TestResultCreate(TestResultBase):
     def test_nobody(self):
         (code, body) = self.create(None)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('no body', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.no_body(), body)
 
     def test_podNotProvided(self):
         req = self.req_d
         req.pod_name = None
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('pod_name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('pod_name'), body)
 
     def test_projectNotProvided(self):
         req = self.req_d
         req.project_name = None
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('project_name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('project_name'), body)
 
     def test_testcaseNotProvided(self):
         req = self.req_d
         req.case_name = None
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('case_name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('case_name'), body)
 
     def test_noPod(self):
         req = self.req_d
         req.pod_name = 'notExistPod'
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_NOT_FOUND)
-        self.assertIn('Could not find pod', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.not_found_base, body)
 
     def test_noProject(self):
         req = self.req_d
         req.project_name = 'notExistProject'
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_NOT_FOUND)
-        self.assertIn('Could not find project', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.not_found_base, body)
 
     def test_noTestcase(self):
         req = self.req_d
         req.case_name = 'notExistTestcase'
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_NOT_FOUND)
-        self.assertIn('Could not find testcase', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.not_found_base, body)
 
     def test_success(self):
         (code, body) = self.create_d()
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_href(body)
 
     def test_key_with_doc(self):
         req = copy.deepcopy(self.req_d)
         req.details = {'1.name': 'dot_name'}
         (code, body) = self.create(req)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_href(body)
 
     def test_no_ti(self):
-        req = ResultCreateRequest(pod_name=self.pod,
-                                  project_name=self.project,
-                                  case_name=self.case,
-                                  installer=self.installer,
-                                  version=self.version,
-                                  start_date=self.start_date,
-                                  stop_date=self.stop_date,
-                                  details=self.details.format(),
-                                  build_tag=self.build_tag,
-                                  scenario=self.scenario,
-                                  criteria=self.criteria)
+        req = result_models.ResultCreateRequest(pod_name=self.pod,
+                                                project_name=self.project,
+                                                case_name=self.case,
+                                                installer=self.installer,
+                                                version=self.version,
+                                                start_date=self.start_date,
+                                                stop_date=self.stop_date,
+                                                details=self.details.format(),
+                                                build_tag=self.build_tag,
+                                                scenario=self.scenario,
+                                                criteria=self.criteria)
         (code, res) = self.create(req)
         _id = res.href.split('/')[-1]
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         code, body = self.get(_id)
         self.assert_res(code, body, req)
 
@@ -236,7 +246,7 @@ class TestResultGet(TestResultBase):
 
     def test_queryPeriodNotInt(self):
         code, body = self.query(self._set_query('period=a'))
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
         self.assertIn('period must be int', body)
 
     def test_queryPeriodFail(self):
@@ -249,7 +259,7 @@ class TestResultGet(TestResultBase):
 
     def test_queryLastNotInt(self):
         code, body = self.query(self._set_query('last=a'))
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
         self.assertIn('last must be int', body)
 
     def test_queryLast(self):
@@ -288,7 +298,7 @@ class TestResultGet(TestResultBase):
             req = self._create_changed_date(**kwargs)
         code, body = self.query(query)
         if not found:
-            self.assertEqual(code, HTTP_OK)
+            self.assertEqual(code, httplib.OK)
             self.assertEqual(0, len(body.results))
         else:
             self.assertEqual(1, len(body.results))
@@ -322,10 +332,11 @@ class TestResultUpdate(TestResultBase):
 
         new_ti = copy.deepcopy(self.trust_indicator)
         new_ti.current += self.update_step
-        new_ti.histories.append(TIHistory(self.update_date, self.update_step))
+        new_ti.histories.append(
+            result_models.TIHistory(self.update_date, self.update_step))
         new_data = copy.deepcopy(self.req_d)
         new_data.trust_indicator = new_ti
-        update = ResultUpdateRequest(trust_indicator=new_ti)
+        update = result_models.ResultUpdateRequest(trust_indicator=new_ti)
         code, body = self.update(update, _id)
         self.assertEqual(_id, body._id)
         self.assert_res(code, body, new_data)
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/test_scenario.py b/utils/test/testapi/opnfv_testapi/tests/unit/test_scenario.py
new file mode 100644 (file)
index 0000000..f2291a5
--- /dev/null
@@ -0,0 +1,360 @@
+from copy import deepcopy
+from datetime import datetime
+import functools
+import httplib
+import json
+import os
+
+from opnfv_testapi.common import message
+import opnfv_testapi.resources.scenario_models as models
+import test_base as base
+
+
+class TestScenarioBase(base.TestBase):
+    def setUp(self):
+        super(TestScenarioBase, self).setUp()
+        self.get_res = models.Scenario
+        self.list_res = models.Scenarios
+        self.basePath = '/api/v1/scenarios'
+        self.req_d = self._load_request('scenario-c1.json')
+        self.req_2 = self._load_request('scenario-c2.json')
+
+    def tearDown(self):
+        pass
+
+    def assert_body(self, project, req=None):
+        pass
+
+    @staticmethod
+    def _load_request(f_req):
+        abs_file = os.path.join(os.path.dirname(__file__), f_req)
+        with open(abs_file, 'r') as f:
+            loader = json.load(f)
+            f.close()
+        return loader
+
+    def create_return_name(self, req):
+        _, res = self.create(req)
+        return res.href.split('/')[-1]
+
+    def assert_res(self, code, scenario, req=None):
+        self.assertEqual(code, httplib.OK)
+        if req is None:
+            req = self.req_d
+        self.assertIsNotNone(scenario._id)
+        self.assertIsNotNone(scenario.creation_date)
+
+        scenario == models.Scenario.from_dict(req)
+
+    @staticmethod
+    def _set_query(*args):
+        uri = ''
+        for arg in args:
+            uri += arg + '&'
+        return uri[0: -1]
+
+    def _get_and_assert(self, name, req=None):
+        code, body = self.get(name)
+        self.assert_res(code, body, req)
+
+
+class TestScenarioCreate(TestScenarioBase):
+    def test_withoutBody(self):
+        (code, body) = self.create()
+        self.assertEqual(code, httplib.BAD_REQUEST)
+
+    def test_emptyName(self):
+        req_empty = models.ScenarioCreateRequest('')
+        (code, body) = self.create(req_empty)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
+
+    def test_noneName(self):
+        req_none = models.ScenarioCreateRequest(None)
+        (code, body) = self.create(req_none)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
+
+    def test_success(self):
+        (code, body) = self.create_d()
+        self.assertEqual(code, httplib.OK)
+        self.assert_create_body(body)
+
+    def test_alreadyExist(self):
+        self.create_d()
+        (code, body) = self.create_d()
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.exist_base, body)
+
+
+class TestScenarioGet(TestScenarioBase):
+    def setUp(self):
+        super(TestScenarioGet, self).setUp()
+        self.scenario_1 = self.create_return_name(self.req_d)
+        self.scenario_2 = self.create_return_name(self.req_2)
+
+    def test_getByName(self):
+        self._get_and_assert(self.scenario_1, self.req_d)
+
+    def test_getAll(self):
+        self._query_and_assert(query=None, reqs=[self.req_d, self.req_2])
+
+    def test_queryName(self):
+        query = self._set_query('name=nosdn-nofeature-ha')
+        self._query_and_assert(query, reqs=[self.req_d])
+
+    def test_queryInstaller(self):
+        query = self._set_query('installer=apex')
+        self._query_and_assert(query, reqs=[self.req_d])
+
+    def test_queryVersion(self):
+        query = self._set_query('version=master')
+        self._query_and_assert(query, reqs=[self.req_d])
+
+    def test_queryProject(self):
+        query = self._set_query('project=functest')
+        self._query_and_assert(query, reqs=[self.req_d, self.req_2])
+
+    def test_queryCombination(self):
+        query = self._set_query('name=nosdn-nofeature-ha',
+                                'installer=apex',
+                                'version=master',
+                                'project=functest')
+
+        self._query_and_assert(query, reqs=[self.req_d])
+
+    def _query_and_assert(self, query, found=True, reqs=None):
+        code, body = self.query(query)
+        if not found:
+            self.assertEqual(code, httplib.OK)
+            self.assertEqual(0, len(body.scenarios))
+        else:
+            self.assertEqual(len(reqs), len(body.scenarios))
+            for req in reqs:
+                for scenario in body.scenarios:
+                    if req['name'] == scenario.name:
+                        self.assert_res(code, scenario, req)
+
+
+class TestScenarioUpdate(TestScenarioBase):
+    def setUp(self):
+        super(TestScenarioUpdate, self).setUp()
+        self.scenario = self.create_return_name(self.req_d)
+        self.scenario_2 = self.create_return_name(self.req_2)
+
+    def _execute(set_update):
+        @functools.wraps(set_update)
+        def magic(self):
+            update, scenario = set_update(self, deepcopy(self.req_d))
+            self._update_and_assert(update, scenario)
+        return magic
+
+    def _update(expected):
+        def _update(set_update):
+            @functools.wraps(set_update)
+            def wrap(self):
+                update, scenario = set_update(self, deepcopy(self.req_d))
+                code, body = self.update(update, self.scenario)
+                getattr(self, expected)(code, scenario)
+            return wrap
+        return _update
+
+    @_update('_success')
+    def test_renameScenario(self, scenario):
+        new_name = 'nosdn-nofeature-noha'
+        scenario['name'] = new_name
+        update_req = models.ScenarioUpdateRequest(field='name',
+                                                  op='update',
+                                                  locate={},
+                                                  term={'name': new_name})
+        return update_req, scenario
+
+    @_update('_forbidden')
+    def test_renameScenario_exist(self, scenario):
+        new_name = self.scenario_2
+        scenario['name'] = new_name
+        update_req = models.ScenarioUpdateRequest(field='name',
+                                                  op='update',
+                                                  locate={},
+                                                  term={'name': new_name})
+        return update_req, scenario
+
+    @_update('_bad_request')
+    def test_renameScenario_noName(self, scenario):
+        new_name = self.scenario_2
+        scenario['name'] = new_name
+        update_req = models.ScenarioUpdateRequest(field='name',
+                                                  op='update',
+                                                  locate={},
+                                                  term={})
+        return update_req, scenario
+
+    @_execute
+    def test_addInstaller(self, scenario):
+        add = models.ScenarioInstaller(installer='daisy', versions=list())
+        scenario['installers'].append(add.format())
+        update = models.ScenarioUpdateRequest(field='installer',
+                                              op='add',
+                                              locate={},
+                                              term=add.format())
+        return update, scenario
+
+    @_execute
+    def test_deleteInstaller(self, scenario):
+        scenario['installers'] = filter(lambda f: f['installer'] != 'apex',
+                                        scenario['installers'])
+
+        update = models.ScenarioUpdateRequest(field='installer',
+                                              op='delete',
+                                              locate={'installer': 'apex'})
+        return update, scenario
+
+    @_execute
+    def test_addVersion(self, scenario):
+        add = models.ScenarioVersion(version='danube', projects=list())
+        scenario['installers'][0]['versions'].append(add.format())
+        update = models.ScenarioUpdateRequest(field='version',
+                                              op='add',
+                                              locate={'installer': 'apex'},
+                                              term=add.format())
+        return update, scenario
+
+    @_execute
+    def test_deleteVersion(self, scenario):
+        scenario['installers'][0]['versions'] = filter(
+            lambda f: f['version'] != 'master',
+            scenario['installers'][0]['versions'])
+
+        update = models.ScenarioUpdateRequest(field='version',
+                                              op='delete',
+                                              locate={'installer': 'apex',
+                                                      'version': 'master'})
+        return update, scenario
+
+    @_execute
+    def test_changeOwner(self, scenario):
+        scenario['installers'][0]['versions'][0]['owner'] = 'lucy'
+
+        update = models.ScenarioUpdateRequest(field='owner',
+                                              op='update',
+                                              locate={'installer': 'apex',
+                                                      'version': 'master'},
+                                              term={'owner': 'lucy'})
+        return update, scenario
+
+    @_execute
+    def test_addProject(self, scenario):
+        add = models.ScenarioProject(project='qtip').format()
+        scenario['installers'][0]['versions'][0]['projects'].append(add)
+        update = models.ScenarioUpdateRequest(field='project',
+                                              op='add',
+                                              locate={'installer': 'apex',
+                                                      'version': 'master'},
+                                              term=add)
+        return update, scenario
+
+    @_execute
+    def test_deleteProject(self, scenario):
+        scenario['installers'][0]['versions'][0]['projects'] = filter(
+            lambda f: f['project'] != 'functest',
+            scenario['installers'][0]['versions'][0]['projects'])
+
+        update = models.ScenarioUpdateRequest(field='project',
+                                              op='delete',
+                                              locate={
+                                                  'installer': 'apex',
+                                                  'version': 'master',
+                                                  'project': 'functest'})
+        return update, scenario
+
+    @_execute
+    def test_addCustoms(self, scenario):
+        add = ['odl', 'parser', 'vping_ssh']
+        projects = scenario['installers'][0]['versions'][0]['projects']
+        functest = filter(lambda f: f['project'] == 'functest', projects)[0]
+        functest['customs'] = ['healthcheck', 'odl', 'parser', 'vping_ssh']
+        update = models.ScenarioUpdateRequest(field='customs',
+                                              op='add',
+                                              locate={
+                                                  'installer': 'apex',
+                                                  'version': 'master',
+                                                  'project': 'functest'},
+                                              term=add)
+        return update, scenario
+
+    @_execute
+    def test_deleteCustoms(self, scenario):
+        projects = scenario['installers'][0]['versions'][0]['projects']
+        functest = filter(lambda f: f['project'] == 'functest', projects)[0]
+        functest['customs'] = ['healthcheck']
+        update = models.ScenarioUpdateRequest(field='customs',
+                                              op='delete',
+                                              locate={
+                                                  'installer': 'apex',
+                                                  'version': 'master',
+                                                  'project': 'functest'},
+                                              term=['vping_ssh'])
+        return update, scenario
+
+    @_execute
+    def test_addScore(self, scenario):
+        add = models.ScenarioScore(date=str(datetime.now()), score='11/12')
+        projects = scenario['installers'][0]['versions'][0]['projects']
+        functest = filter(lambda f: f['project'] == 'functest', projects)[0]
+        functest['scores'].append(add.format())
+        update = models.ScenarioUpdateRequest(field='score',
+                                              op='add',
+                                              locate={
+                                                  'installer': 'apex',
+                                                  'version': 'master',
+                                                  'project': 'functest'},
+                                              term=add.format())
+        return update, scenario
+
+    @_execute
+    def test_addTi(self, scenario):
+        add = models.ScenarioTI(date=str(datetime.now()), status='gold')
+        projects = scenario['installers'][0]['versions'][0]['projects']
+        functest = filter(lambda f: f['project'] == 'functest', projects)[0]
+        functest['trust_indicators'].append(add.format())
+        update = models.ScenarioUpdateRequest(field='trust_indicator',
+                                              op='add',
+                                              locate={
+                                                  'installer': 'apex',
+                                                  'version': 'master',
+                                                  'project': 'functest'},
+                                              term=add.format())
+        return update, scenario
+
+    def _update_and_assert(self, update_req, new_scenario, name=None):
+        code, _ = self.update(update_req, self.scenario)
+        self.assertEqual(code, httplib.OK)
+        self._get_and_assert(_none_default(name, self.scenario),
+                             new_scenario)
+
+    def _success(self, status, new_scenario):
+        self.assertEqual(status, httplib.OK)
+        self._get_and_assert(new_scenario.get('name'), new_scenario)
+
+    def _forbidden(self, status, new_scenario):
+        self.assertEqual(status, httplib.FORBIDDEN)
+
+    def _bad_request(self, status, new_scenario):
+        self.assertEqual(status, httplib.BAD_REQUEST)
+
+
+class TestScenarioDelete(TestScenarioBase):
+    def test_notFound(self):
+        code, body = self.delete('notFound')
+        self.assertEqual(code, httplib.NOT_FOUND)
+
+    def test_success(self):
+        scenario = self.create_return_name(self.req_d)
+        code, _ = self.delete(scenario)
+        self.assertEqual(code, httplib.OK)
+        code, _ = self.get(scenario)
+        self.assertEqual(code, httplib.NOT_FOUND)
+
+
+def _none_default(check, default):
+    return check if check else default
index cb76784..62d0fa0 100644 (file)
@@ -6,35 +6,34 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
-import unittest
 import copy
+import httplib
+import unittest
 
-from test_base import TestBase
-from opnfv_testapi.resources.testcase_models import TestcaseCreateRequest, \
-    Testcase, Testcases, TestcaseUpdateRequest
-from opnfv_testapi.resources.project_models import ProjectCreateRequest
-from opnfv_testapi.common.constants import HTTP_OK, HTTP_BAD_REQUEST, \
-    HTTP_FORBIDDEN, HTTP_NOT_FOUND
+from opnfv_testapi.common import message
+from opnfv_testapi.resources import project_models
+from opnfv_testapi.resources import testcase_models
+import test_base as base
 
 
-class TestCaseBase(TestBase):
+class TestCaseBase(base.TestBase):
     def setUp(self):
         super(TestCaseBase, self).setUp()
-        self.req_d = TestcaseCreateRequest('vping_1',
-                                           '/cases/vping_1',
-                                           'vping-ssh test')
-        self.req_e = TestcaseCreateRequest('doctor_1',
-                                           '/cases/doctor_1',
-                                           'create doctor')
-        self.update_d = TestcaseUpdateRequest('vping_1',
-                                              'vping-ssh test',
-                                              'functest')
-        self.update_e = TestcaseUpdateRequest('doctor_1',
-                                              'create doctor',
-                                              'functest')
-        self.get_res = Testcase
-        self.list_res = Testcases
-        self.update_res = Testcase
+        self.req_d = testcase_models.TestcaseCreateRequest('vping_1',
+                                                           '/cases/vping_1',
+                                                           'vping-ssh test')
+        self.req_e = testcase_models.TestcaseCreateRequest('doctor_1',
+                                                           '/cases/doctor_1',
+                                                           'create doctor')
+        self.update_d = testcase_models.TestcaseUpdateRequest('vping_1',
+                                                              'vping-ssh test',
+                                                              'functest')
+        self.update_e = testcase_models.TestcaseUpdateRequest('doctor_1',
+                                                              'create doctor',
+                                                              'functest')
+        self.get_res = testcase_models.Testcase
+        self.list_res = testcase_models.Testcases
+        self.update_res = testcase_models.Testcase
         self.basePath = '/api/v1/projects/%s/cases'
         self.create_project()
 
@@ -57,7 +56,8 @@ class TestCaseBase(TestBase):
         self.assertIsNotNone(new.creation_date)
 
     def create_project(self):
-        req_p = ProjectCreateRequest('functest', 'vping-ssh test')
+        req_p = project_models.ProjectCreateRequest('functest',
+                                                    'vping-ssh test')
         self.create_help('/api/v1/projects', req_p)
         self.project = req_p.name
 
@@ -80,46 +80,46 @@ class TestCaseBase(TestBase):
 class TestCaseCreate(TestCaseBase):
     def test_noBody(self):
         (code, body) = self.create(None, 'vping')
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
 
     def test_noProject(self):
         code, body = self.create(self.req_d, 'noProject')
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn('Could not find project', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.not_found_base, body)
 
     def test_emptyName(self):
-        req_empty = TestcaseCreateRequest('')
+        req_empty = testcase_models.TestcaseCreateRequest('')
         (code, body) = self.create(req_empty, self.project)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
 
     def test_noneName(self):
-        req_none = TestcaseCreateRequest(None)
+        req_none = testcase_models.TestcaseCreateRequest(None)
         (code, body) = self.create(req_none, self.project)
-        self.assertEqual(code, HTTP_BAD_REQUEST)
-        self.assertIn('name missing', body)
+        self.assertEqual(code, httplib.BAD_REQUEST)
+        self.assertIn(message.missing('name'), body)
 
     def test_success(self):
         code, body = self.create_d()
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_create_body(body, None, self.project)
 
     def test_alreadyExist(self):
         self.create_d()
         code, body = self.create_d()
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn('already exists', body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.exist_base, body)
 
 
 class TestCaseGet(TestCaseBase):
     def test_notExist(self):
         code, body = self.get('notExist')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_getOne(self):
         self.create_d()
         code, body = self.get(self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assert_body(body)
 
     def test_list(self):
@@ -136,24 +136,24 @@ class TestCaseGet(TestCaseBase):
 class TestCaseUpdate(TestCaseBase):
     def test_noBody(self):
         code, _ = self.update(case='noBody')
-        self.assertEqual(code, HTTP_BAD_REQUEST)
+        self.assertEqual(code, httplib.BAD_REQUEST)
 
     def test_notFound(self):
         code, _ = self.update(self.update_e, 'notFound')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_newNameExist(self):
         self.create_d()
         self.create_e()
         code, body = self.update(self.update_e, self.req_d.name)
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn("already exists", body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.exist_base, body)
 
     def test_noUpdate(self):
         self.create_d()
         code, body = self.update(self.update_d, self.req_d.name)
-        self.assertEqual(code, HTTP_FORBIDDEN)
-        self.assertIn("Nothing to update", body)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.no_update(), body)
 
     def test_success(self):
         self.create_d()
@@ -161,7 +161,7 @@ class TestCaseUpdate(TestCaseBase):
         _id = body._id
 
         code, body = self.update(self.update_e, self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assertEqual(_id, body._id)
         self.assert_update_body(self.req_d, body, self.update_e)
 
@@ -174,22 +174,22 @@ class TestCaseUpdate(TestCaseBase):
         update = copy.deepcopy(self.update_d)
         update.description = {'2. change': 'dollar change'}
         code, body = self.update(update, self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
 
 
 class TestCaseDelete(TestCaseBase):
     def test_notFound(self):
         code, body = self.delete('notFound')
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
     def test_success(self):
         self.create_d()
         code, body = self.delete(self.req_d.name)
-        self.assertEqual(code, HTTP_OK)
+        self.assertEqual(code, httplib.OK)
         self.assertEqual(body, '')
 
         code, body = self.get(self.req_d.name)
-        self.assertEqual(code, HTTP_NOT_FOUND)
+        self.assertEqual(code, httplib.NOT_FOUND)
 
 
 if __name__ == '__main__':
diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/test_token.py b/utils/test/testapi/opnfv_testapi/tests/unit/test_token.py
new file mode 100644 (file)
index 0000000..ed3eda0
--- /dev/null
@@ -0,0 +1,119 @@
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import httplib
+import unittest
+
+from tornado import web
+
+import fake_pymongo
+from opnfv_testapi.common import message
+from opnfv_testapi.resources import project_models
+from opnfv_testapi.router import url_mappings
+import test_base as base
+
+
+class TestToken(base.TestBase):
+    def get_app(self):
+        return web.Application(
+            url_mappings.mappings,
+            db=fake_pymongo,
+            debug=True,
+            auth=True
+        )
+
+
+class TestTokenCreateProject(TestToken):
+    def setUp(self):
+        super(TestTokenCreateProject, self).setUp()
+        self.req_d = project_models.ProjectCreateRequest('vping')
+        fake_pymongo.tokens.insert({"access_token": "12345"})
+        self.basePath = '/api/v1/projects'
+
+    def test_projectCreateTokenInvalid(self):
+        self.headers['X-Auth-Token'] = '1234'
+        code, body = self.create_d()
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.invalid_token(), body)
+
+    def test_projectCreateTokenUnauthorized(self):
+        self.headers.pop('X-Auth-Token')
+        code, body = self.create_d()
+        self.assertEqual(code, httplib.UNAUTHORIZED)
+        self.assertIn(message.unauthorized(), body)
+
+    def test_projectCreateTokenSuccess(self):
+        self.headers['X-Auth-Token'] = '12345'
+        code, body = self.create_d()
+        self.assertEqual(code, httplib.OK)
+
+
+class TestTokenDeleteProject(TestToken):
+    def setUp(self):
+        super(TestTokenDeleteProject, self).setUp()
+        self.req_d = project_models.ProjectCreateRequest('vping')
+        fake_pymongo.tokens.insert({"access_token": "12345"})
+        self.basePath = '/api/v1/projects'
+
+    def test_projectDeleteTokenIvalid(self):
+        self.headers['X-Auth-Token'] = '12345'
+        self.create_d()
+        self.headers['X-Auth-Token'] = '1234'
+        code, body = self.delete(self.req_d.name)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.invalid_token(), body)
+
+    def test_projectDeleteTokenUnauthorized(self):
+        self.headers['X-Auth-Token'] = '12345'
+        self.create_d()
+        self.headers.pop('X-Auth-Token')
+        code, body = self.delete(self.req_d.name)
+        self.assertEqual(code, httplib.UNAUTHORIZED)
+        self.assertIn(message.unauthorized(), body)
+
+    def test_projectDeleteTokenSuccess(self):
+        self.headers['X-Auth-Token'] = '12345'
+        self.create_d()
+        code, body = self.delete(self.req_d.name)
+        self.assertEqual(code, httplib.OK)
+
+
+class TestTokenUpdateProject(TestToken):
+    def setUp(self):
+        super(TestTokenUpdateProject, self).setUp()
+        self.req_d = project_models.ProjectCreateRequest('vping')
+        fake_pymongo.tokens.insert({"access_token": "12345"})
+        self.basePath = '/api/v1/projects'
+
+    def test_projectUpdateTokenIvalid(self):
+        self.headers['X-Auth-Token'] = '12345'
+        self.create_d()
+        code, body = self.get(self.req_d.name)
+        self.headers['X-Auth-Token'] = '1234'
+        req = project_models.ProjectUpdateRequest('newName', 'new description')
+        code, body = self.update(req, self.req_d.name)
+        self.assertEqual(code, httplib.FORBIDDEN)
+        self.assertIn(message.invalid_token(), body)
+
+    def test_projectUpdateTokenUnauthorized(self):
+        self.headers['X-Auth-Token'] = '12345'
+        self.create_d()
+        code, body = self.get(self.req_d.name)
+        self.headers.pop('X-Auth-Token')
+        req = project_models.ProjectUpdateRequest('newName', 'new description')
+        code, body = self.update(req, self.req_d.name)
+        self.assertEqual(code, httplib.UNAUTHORIZED)
+        self.assertIn(message.unauthorized(), body)
+
+    def test_projectUpdateTokenSuccess(self):
+        self.headers['X-Auth-Token'] = '12345'
+        self.create_d()
+        code, body = self.get(self.req_d.name)
+        req = project_models.ProjectUpdateRequest('newName', 'new description')
+        code, body = self.update(req, self.req_d.name)
+        self.assertEqual(code, httplib.OK)
+
+if __name__ == '__main__':
+    unittest.main()
index b6fbf45..c8f3f50 100644 (file)
@@ -8,14 +8,14 @@
 ##############################################################################
 import unittest
 
-from test_base import TestBase
-from opnfv_testapi.resources.models import Versions
+from opnfv_testapi.resources import models
+import test_base as base
 
 
-class TestVersionBase(TestBase):
+class TestVersionBase(base.TestBase):
     def setUp(self):
         super(TestVersionBase, self).setUp()
-        self.list_res = Versions
+        self.list_res = models.Versions
         self.basePath = '/versions'
 
 
index 9b25f8f..1e05dd6 100755 (executable)
@@ -1,10 +1,40 @@
-#! /bin/bash
+#!/bin/bash
 
-# Before run this script, make sure that testtools and discover
-# had been installed in your env
-# or else using pip to install them as follows:
-# pip install testtools, discover
+set -o errexit
+
+# Get script directory
+SCRIPTDIR=`dirname $0`
+
+echo "Running unit tests..."
+
+# Creating virtual environment
+if [ ! -z $VIRTUAL_ENV ]; then
+    venv=$VIRTUAL_ENV
+else
+    venv=$SCRIPTDIR/.venv
+    virtualenv $venv
+fi
+source $venv/bin/activate
+
+# Install requirements
+pip install -r $SCRIPTDIR/requirements.txt
+pip install -r $SCRIPTDIR/test-requirements.txt
 
 find . -type f -name "*.pyc" -delete
-testrargs="discover ./opnfv_testapi/tests/unit"
-python -m testtools.run $testrargs
+
+nosetests --with-xunit \
+    --with-coverage \
+    --cover-erase \
+    --cover-package=$SCRIPTDIR/opnfv_testapi/cmd \
+    --cover-package=$SCRIPTDIR/opnfv_testapi/common \
+    --cover-package=$SCRIPTDIR/opnfv_testapi/resources \
+    --cover-package=$SCRIPTDIR/opnfv_testapi/router \
+    --cover-xml \
+    --cover-html \
+    $SCRIPTDIR/opnfv_testapi/tests
+
+exit_code=$?
+
+deactivate
+
+exit $exit_code
index ddbdefc..645687b 100644 (file)
@@ -2,6 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 
-testtools>=1.4.0
-discover
-futures
\ No newline at end of file
+mock
+pytest
+coverage
+nose>=1.3.1
diff --git a/utils/test/testapi/tox.ini b/utils/test/testapi/tox.ini
new file mode 100644 (file)
index 0000000..81c9dfa
--- /dev/null
@@ -0,0 +1,41 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27,pep8
+skipsdist = True
+sitepackages = True
+
+[testenv]
+usedevelop = True
+install_command = pip install -U {opts} {packages}
+deps =
+  -rrequirements.txt
+  -rtest-requirements.txt
+commands=
+  py.test \
+    --basetemp={envtmpdir} \
+    --cov \
+    {posargs}
+setenv=
+  HOME = {envtmpdir}
+  PYTHONPATH = {toxinidir}
+
+[testenv:pep8]
+deps = flake8
+commands = flake8 {toxinidir}
+
+[flake8]
+# H803 skipped on purpose per list discussion.
+# E123, E125 skipped as they are invalid PEP-8.
+
+show-source = True
+ignore = E123,E125,H803,E501
+builtins = _
+exclude = build,dist,doc,legacy,.eggs,.git,.tox,.venv,testapi_venv,venv
+
+[pytest]
+testpaths = opnfv_testapi/tests
+python_functions = test_*
index a18ff03..4254fee 100644 (file)
@@ -44,5 +44,5 @@ def main(method, parser):
     args = parser.parse_args()
     try:
         method(args)
-    except AssertionError, msg:
+    except AssertionError as msg:
         print(msg)
index a886872..943105c 100644 (file)
@@ -1,7 +1,7 @@
 ---
 - hosts: "{{ host }}"
   remote_user: "{{ user }}"
-  become: yes
+  become: "yes"
   become_method: sudo
   vars:
     user: "root"
index e6663d9..18b75b6 100644 (file)
@@ -1,7 +1,7 @@
 ---
 - hosts: "{{ host }}"
   remote_user: "{{ user }}"
-  become: yes
+  become: "yes"
   become_method: sudo
   vars:
     user: "root"
@@ -47,4 +47,4 @@
     - name: remove temporary update directory
       file:
         path: "{{ update_path }}"
-        state: absent
\ No newline at end of file
+        state: absent
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/.dockerignore b/utils/test/vnfcatalogue/VNF_Catalogue/.dockerignore
new file mode 100644 (file)
index 0000000..93f1361
--- /dev/null
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/Dockerfile b/utils/test/vnfcatalogue/VNF_Catalogue/Dockerfile
new file mode 100644 (file)
index 0000000..5df8eb5
--- /dev/null
@@ -0,0 +1,35 @@
+#############################################
+#   Docker container for VNF_Catalogue WebApp
+#############################################
+# Purpose: Don't run it from here! Use docker-compose(See README.md)
+#
+# Maintained by Kumar Rishabh :: penguinRaider
+##
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+FROM node:boron
+MAINTAINER KumarRishabh::penguinRaider <shailrishabh@gmail.com>
+LABEL version="v0.0.1" description="Open Source VNF_Catalogue for OPNFV"
+
+ENV DB_HOST mysql
+ENV DB_USER vnf_user
+ENV DB_PASSWORD vnf_password
+ENV DB_DATABASE vnf_catalogue
+
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+COPY package.json /usr/src/app/
+
+RUN npm install
+
+COPY . /usr/src/app
+
+EXPOSE 3000
+
+# We wait for mysql service to come up before starting the server using a 3rd_party script.
+CMD [ "./migration/3rd_party/wait-for-it/wait-for-it.sh", "mysql:3306", "-t", "0", "--", "npm", "start" ]
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/README.md b/utils/test/vnfcatalogue/VNF_Catalogue/README.md
new file mode 100644 (file)
index 0000000..91179f5
--- /dev/null
@@ -0,0 +1,18 @@
+#VNF_Catalogue Nodejs + Jade + MySql server
+
+
+## Quickstart
+
+First install docker and docker-compose. This multicontainer app uses
+docker-compose to organize the vnf_catalogue web_app
+
+The use
+    ```docker-compose up```
+
+set time zone(optional)
+        Set same timezone in both nodejs server and mysql server. Something
+        similar to below can be used:
+        ``` SET GLOBAL time_zone = '+00:00'; ```
+
+
+The server would be accessible at ```ip_address:3000```
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/app.js b/utils/test/vnfcatalogue/VNF_Catalogue/app.js
new file mode 100644 (file)
index 0000000..4b6add2
--- /dev/null
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var path = require('path');
+var favicon = require('serve-favicon');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var validator = require('express-validator');
+
+var routes = require('./routes/index');
+var search_projects = require('./routes/search_projects');
+var project_profile = require('./routes/project_profile');
+var add_project = require('./routes/add_project');
+var add_tag = require('./routes/add_tag');
+var search_tag = require('./routes/search_tag');
+var search_vnf = require('./routes/search_vnf');
+var vnf_tag_association = require('./routes/vnf_tag_association');
+
+
+var app = express();
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'jade');
+
+db_pool = require('./database').pool;
+
+// Database
+//var db = require('mysql2');
+
+// uncomment after placing your favicon in /public
+//app.use(favicon(__dirname + '/public/favicon.ico'));
+app.use(logger('dev'));
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(validator());
+app.use(cookieParser());
+app.use(express.static(path.join(__dirname, 'public')));
+
+// Make our db accessible to our router
+app.use(function(req,res,next){
+    //db_pool size 50 default
+    req.db_pool = db_pool;
+    next();
+});
+
+app.use('/', routes);
+app.use('/search_projects', search_projects);
+app.use('/project_profile', project_profile);
+app.use('/add_project', add_project);
+app.use('/add_tag', add_tag);
+app.use('/search_tag', search_tag);
+app.use('/search_vnf', search_vnf);
+app.use('/vnf_tag_association', vnf_tag_association);
+// Some Error handling for now #TODO Remove
+
+/// catch 404 and forwarding to error handler
+app.use(function(req, res, next) {
+    var err = new Error('Not Found');
+    err.status = 404;
+    next(err);
+});
+
+
+// development error handler
+// will print stacktrace
+if (app.get('env') === 'development') {
+    app.use(function(err, req, res, next) {
+        res.status(err.status || 500);
+        res.render('error', {
+            message: err.message,
+            error: err
+        });
+    });
+}
+
+// production error handler
+// no stacktraces leaked to user
+app.use(function(err, req, res, next) {
+    res.status(err.status || 500);
+    res.render('error', {
+        message: err.message,
+        error: {}
+    });
+});
+
+module.exports = app;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/bin/www b/utils/test/vnfcatalogue/VNF_Catalogue/bin/www
new file mode 100644 (file)
index 0000000..3cfbf77
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env node
+var debug = require('debug')('my-application');
+var app = require('../app');
+
+app.set('port', process.env.PORT || 3000);
+
+var server = app.listen(app.get('port'), function() {
+  debug('Express server listening on port ' + server.address().port);
+});
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/database.js b/utils/test/vnfcatalogue/VNF_Catalogue/database.js
new file mode 100644 (file)
index 0000000..4fa8be1
--- /dev/null
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var mysql = require('mysql');
+
+var pool = mysql.createPool({
+  host: process.env.DB_HOST,
+  user: process.env.DB_USER,
+  password: process.env.DB_PASSWORD,
+  database: process.env.DB_DATABASE,
+  connectionLimit: 50,
+  supportBigNumbers: true,
+  multipleStatements: true,
+  dateStrings: 'date'
+});
+
+exports.pool = pool;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/docker-compose.yml b/utils/test/vnfcatalogue/VNF_Catalogue/docker-compose.yml
new file mode 100644 (file)
index 0000000..a0ddfa4
--- /dev/null
@@ -0,0 +1,49 @@
+##################################################################
+#   Docker-Compose for setting up and running vnf_catalogue webapp
+##################################################################
+# Purpose: To setup and run vnf_catalogue webapp
+# Usage: docker-compose up
+#
+# The webapp would be accessible at ip_address:3000
+#
+# Maintained by Kumar Rishabh :: penguinRaider
+##
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+version: "2"
+services:
+    mysql:
+    # mysql service built on top of vanilla mysql container.
+    # Defines mysql credentials.
+
+        image: mysql
+        container_name: vnf_catalogue_database
+        environment:
+            - MYSQL_USER=vnf_user
+            - MYSQL_PASSWORD=vnf_password
+            - MYSQL_DATABASE=vnf_catalogue
+            - MYSQL_ROOT_PASSWORD=root
+    vnf_catalogue:
+    # The vnf_catalogue webapp service.
+        build: .
+        container_name: vnf_catalogue_webapp
+        depends_on:
+            - mysql
+        # Port on which the node.js server would be exposed <host:container>
+        # To change host port to say 80 use 80:3000
+
+        ports:
+            - "3000:3000"
+    vnf_catalogue_migrate:
+    # We define another service for database migration. This service will
+    # migrate the database schema.(Dockerfile resides in migration
+    # directory).
+
+        build: ./migration
+        container_name: vnf_catalogue_migrate
+        depends_on:
+            - mysql
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/docker_commands.sh b/utils/test/vnfcatalogue/VNF_Catalogue/docker_commands.sh
new file mode 100644 (file)
index 0000000..3be98ce
--- /dev/null
@@ -0,0 +1,2 @@
+sh ./3rd_party/wait-for-it/wait-for-it.sh mysql:3306 -t 0
+node migrate
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/LICENSE b/utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/LICENSE
new file mode 100644 (file)
index 0000000..bd18d0c
--- /dev/null
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+Copyright (c) 2016 Giles Hall
+
+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/utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/README.md b/utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/README.md
new file mode 100644 (file)
index 0000000..900b757
--- /dev/null
@@ -0,0 +1,75 @@
+# HELP NEEDED
+
+### March 13, 2017 update
+
+Since I posted this request for help, I've had a dozen or so responses which I am now sorting through.  Applicants need to fill out [this](https://goo.gl/forms/GKCBFxaloaU47aky1) survey by March 17th.  I will select, notify and announce the new volunteer(s) on March 19th.  Once this is done, me and my team will work through the backlog of issues amd pull requests.  Thanks for your paitence.
+
+### Old request follows:
+
+Hi there!  I wrote `wait-for-it` in order to help me orchestrate containers I operate at my day job.  I thought it was a neat little script, so I published it.  I assumed I would be its only user, but that's not what happened!  `wait-for-it` has received more stars then all of my other public repositories put together.  I had no idea this tool would solicit such an audience, and I was equally unprepared to carve out the time required to address my user's issues and patches.  I would like to solicit a volunteer from the community who would be willing to be a co-maintainer of this repository.  If this is something you might be interested in, please email me at `waitforit@polymerase.org`.  Thanks!
+
+## wait-for-it
+
+`wait-for-it.sh` is a pure bash script that will wait on the availability of a host and TCP port.  It is useful for synchronizing the spin-up of interdependent services, such as linked docker containers.  Since it is a pure bash script, it does not have any external dependencies.
+
+## Usage
+
+```
+wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
+-h HOST | --host=HOST       Host or IP under test
+-p PORT | --port=PORT       TCP port under test
+                            Alternatively, you specify the host and port as host:port
+-s | --strict               Only execute subcommand if the test succeeds
+-q | --quiet                Don't output any status messages
+-t TIMEOUT | --timeout=TIMEOUT
+                            Timeout in seconds, zero for no timeout
+-- COMMAND ARGS             Execute command with args after the test finishes
+```
+
+## Examples
+
+For example, let's test to see if we can access port 80 on www.google.com, and if it is available, echo the message `google is up`.
+
+```
+$ ./wait-for-it.sh www.google.com:80 -- echo "google is up"
+wait-for-it.sh: waiting 15 seconds for www.google.com:80
+wait-for-it.sh: www.google.com:80 is available after 0 seconds
+google is up
+```
+
+You can set your own timeout with the `-t` or `--timeout=` option.  Setting the timeout value to 0 will disable the timeout:
+
+```
+$ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up"
+wait-for-it.sh: waiting for www.google.com:80 without a timeout
+wait-for-it.sh: www.google.com:80 is available after 0 seconds
+google is up
+```
+
+The subcommand will be executed regardless if the service is up or not.  If you wish to execute the subcommand only if the service is up, add the `--strict` argument. In this example, we will test port 81 on www.google.com which will fail:
+
+```
+$ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up"
+wait-for-it.sh: waiting 1 seconds for www.google.com:81
+wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81
+wait-for-it.sh: strict mode, refusing to execute subprocess
+```
+
+If you don't want to execute a subcommand, leave off the `--` argument.  This way, you can test the exit condition of `wait-for-it.sh` in your own scripts, and determine how to proceed:
+
+```
+$ ./wait-for-it.sh www.google.com:80
+wait-for-it.sh: waiting 15 seconds for www.google.com:80
+wait-for-it.sh: www.google.com:80 is available after 0 seconds
+$ echo $?
+0
+$ ./wait-for-it.sh www.google.com:81
+wait-for-it.sh: waiting 15 seconds for www.google.com:81
+wait-for-it.sh: timeout occurred after waiting 15 seconds for www.google.com:81
+$ echo $?
+124
+```
+
+## Thanks
+
+I wrote this script for my employer, [Ginkgo Bioworks](http://www.ginkgobioworks.com/), who was kind enough to let me release it as an open source tool.  We are always looking to [hire](https://jobs.lever.co/ginkgobioworks) talented folks who are interested in working in the field of synthetic biology.
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/wait-for-it.sh b/utils/test/vnfcatalogue/VNF_Catalogue/migration/3rd_party/wait-for-it/wait-for-it.sh
new file mode 100755 (executable)
index 0000000..eca6c3b
--- /dev/null
@@ -0,0 +1,161 @@
+#!/usr/bin/env bash
+#   Use this script to test if a given TCP host/port are available
+
+cmdname=$(basename $0)
+
+echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
+
+usage()
+{
+    cat << USAGE >&2
+Usage:
+    $cmdname host:port [-s] [-t timeout] [-- command args]
+    -h HOST | --host=HOST       Host or IP under test
+    -p PORT | --port=PORT       TCP port under test
+                                Alternatively, you specify the host and port as host:port
+    -s | --strict               Only execute subcommand if the test succeeds
+    -q | --quiet                Don't output any status messages
+    -t TIMEOUT | --timeout=TIMEOUT
+                                Timeout in seconds, zero for no timeout
+    -- COMMAND ARGS             Execute command with args after the test finishes
+USAGE
+    exit 1
+}
+
+wait_for()
+{
+    if [[ $TIMEOUT -gt 0 ]]; then
+        echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT"
+    else
+        echoerr "$cmdname: waiting for $HOST:$PORT without a timeout"
+    fi
+    start_ts=$(date +%s)
+    while :
+    do
+        (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1
+        result=$?
+        if [[ $result -eq 0 ]]; then
+            end_ts=$(date +%s)
+            echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds"
+            break
+        fi
+        sleep 1
+    done
+    return $result
+}
+
+wait_for_wrapper()
+{
+    # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
+    if [[ $QUIET -eq 1 ]]; then
+        timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
+    else
+        timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
+    fi
+    PID=$!
+    trap "kill -INT -$PID" INT
+    wait $PID
+    RESULT=$?
+    if [[ $RESULT -ne 0 ]]; then
+        echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT"
+    fi
+    return $RESULT
+}
+
+# process arguments
+while [[ $# -gt 0 ]]
+do
+    case "$1" in
+        *:* )
+        hostport=(${1//:/ })
+        HOST=${hostport[0]}
+        PORT=${hostport[1]}
+        shift 1
+        ;;
+        --child)
+        CHILD=1
+        shift 1
+        ;;
+        -q | --quiet)
+        QUIET=1
+        shift 1
+        ;;
+        -s | --strict)
+        STRICT=1
+        shift 1
+        ;;
+        -h)
+        HOST="$2"
+        if [[ $HOST == "" ]]; then break; fi
+        shift 2
+        ;;
+        --host=*)
+        HOST="${1#*=}"
+        shift 1
+        ;;
+        -p)
+        PORT="$2"
+        if [[ $PORT == "" ]]; then break; fi
+        shift 2
+        ;;
+        --port=*)
+        PORT="${1#*=}"
+        shift 1
+        ;;
+        -t)
+        TIMEOUT="$2"
+        if [[ $TIMEOUT == "" ]]; then break; fi
+        shift 2
+        ;;
+        --timeout=*)
+        TIMEOUT="${1#*=}"
+        shift 1
+        ;;
+        --)
+        shift
+        CLI="$@"
+        break
+        ;;
+        --help)
+        usage
+        ;;
+        *)
+        echoerr "Unknown argument: $1"
+        usage
+        ;;
+    esac
+done
+
+if [[ "$HOST" == "" || "$PORT" == "" ]]; then
+    echoerr "Error: you need to provide a host and port to test."
+    usage
+fi
+
+TIMEOUT=${TIMEOUT:-15}
+STRICT=${STRICT:-0}
+CHILD=${CHILD:-0}
+QUIET=${QUIET:-0}
+
+if [[ $CHILD -gt 0 ]]; then
+    wait_for
+    RESULT=$?
+    exit $RESULT
+else
+    if [[ $TIMEOUT -gt 0 ]]; then
+        wait_for_wrapper
+        RESULT=$?
+    else
+        wait_for
+        RESULT=$?
+    fi
+fi
+
+if [[ $CLI != "" ]]; then
+    if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then
+        echoerr "$cmdname: strict mode, refusing to execute subprocess"
+        exit $RESULT
+    fi
+    exec $CLI
+else
+    exit $RESULT
+fi
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/migration/Dockerfile b/utils/test/vnfcatalogue/VNF_Catalogue/migration/Dockerfile
new file mode 100644 (file)
index 0000000..a046664
--- /dev/null
@@ -0,0 +1,34 @@
+###############################################################
+#   Docker container for VNF_Catalogue Schema Migration Service
+###############################################################
+# Purpose: Don't run it from here! Use docker-compose(See README.md)
+#
+# Maintained by Kumar Rishabh :: penguinRaider
+##
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+FROM node:boron
+MAINTAINER KumarRishabh::penguinRaider <shailrishabh@gmail.com>
+LABEL version="v0.0.1" description="Open Source VNF_Catalogue for OPNFV"
+
+ENV DB_HOST mysql
+ENV DB_USER vnf_user
+ENV DB_PASSWORD vnf_password
+ENV DB_DATABASE vnf_catalogue
+
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+COPY package.json /usr/src/app/
+
+RUN npm install
+
+COPY . /usr/src/app
+
+# The ordering of events should be coming up of mysql service and then migration
+# of schema for the database. To enforce this causal relationship we use a 3rd_party script.
+CMD [ "./3rd_party/wait-for-it/wait-for-it.sh", "mysql:3306", "-t", "0", "--", "node", "migrate"]
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/migration/migrate.js b/utils/test/vnfcatalogue/VNF_Catalogue/migration/migrate.js
new file mode 100644 (file)
index 0000000..3f4d892
--- /dev/null
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh(penguinRaider) and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var knex = require('knex')({
+    client: 'mysql',
+    connection: {
+        host     : process.env.DB_HOST,
+        user     : process.env.DB_USER,
+        password : process.env.DB_PASSWORD,
+        database : process.env.DB_DATABASE,
+        charset  : 'utf8'
+    }
+});
+var Schema = require('./schema');
+var sequence = require('when/sequence');
+var _ = require('lodash');
+function createTable(tableName) {
+    return knex.schema.createTable(tableName, function (table) {
+    var column;
+    var columnKeys = _.keys(Schema[tableName]);
+    _.each(columnKeys, function (key) {
+        if (Schema[tableName][key].type === 'text' && Schema[tableName][key].hasOwnProperty('fieldtype')) {
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].fieldtype);
+        }
+        else if (Schema[tableName][key].type === 'enum' && Schema[tableName][key].hasOwnProperty('values') && Schema[tableName][key].nullable === true) {
+        console.log(Schema[tableName][key].values);
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].values).nullable();
+        }
+        else if (Schema[tableName][key].type === 'enum' && Schema[tableName][key].hasOwnProperty('values')) {
+        console.log(Schema[tableName][key].values);
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].values).notNullable();
+        }
+        else if (Schema[tableName][key].type === 'string' && Schema[tableName][key].hasOwnProperty('maxlength')) {
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].maxlength);
+        }
+        else {
+        column = table[Schema[tableName][key].type](key);
+        }
+        if (Schema[tableName][key].hasOwnProperty('nullable') && Schema[tableName][key].nullable === true) {
+        column.nullable();
+        }
+        else {
+        column.notNullable();
+        }
+        if (Schema[tableName][key].hasOwnProperty('primary') && Schema[tableName][key].primary === true) {
+        column.primary();
+        }
+        if (Schema[tableName][key].hasOwnProperty('unique') && Schema[tableName][key].unique) {
+        column.unique();
+        }
+        if (Schema[tableName][key].hasOwnProperty('unsigned') && Schema[tableName][key].unsigned) {
+        column.unsigned();
+        }
+        if (Schema[tableName][key].hasOwnProperty('references')) {
+        column.references(Schema[tableName][key].references);
+        }
+        if (Schema[tableName][key].hasOwnProperty('defaultTo')) {
+        column.defaultTo(Schema[tableName][key].defaultTo);
+        }
+    });
+    });
+}
+function createTables () {
+    var tables = [];
+    var tableNames = _.keys(Schema);
+    tables = _.map(tableNames, function (tableName) {
+    return function () {
+        return createTable(tableName);
+    };
+    });
+    return sequence(tables);
+}
+createTables()
+.then(function() {
+    console.log('Tables created!!');
+    process.exit(0);
+})
+.catch(function (error) {
+    throw error;
+});
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/migration/package.json b/utils/test/vnfcatalogue/VNF_Catalogue/migration/package.json
new file mode 100644 (file)
index 0000000..65ff324
--- /dev/null
@@ -0,0 +1,15 @@
+{
+  "name": "VNF_Catalogue_migration",
+  "version": "0.0.1",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www"
+  },
+  "dependencies": {
+    "bookshelf": "*",
+    "knex": "*",
+    "lodash": "*",
+    "mysql": "^2.13.0",
+    "when": "*"
+  }
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/migration/schema.js b/utils/test/vnfcatalogue/VNF_Catalogue/migration/schema.js
new file mode 100644 (file)
index 0000000..4a7559a
--- /dev/null
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh(penguinRaider) and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+var Schema = {
+    photo: {
+        photo_id: {type: 'increments', nullable: false, primary: true},
+        photo_url: {type: 'string', maxlength: 254, nullable: false}
+    },
+    user: {
+        user_id: {type: 'increments', nullable: false, primary: true},
+        user_name: {type: 'string', maxlength: 254, nullable: false},
+        password: {type: 'string', maxlength: 150, nullable: false},
+        email_id: {type: 'string', maxlength: 254, nullable: false, unique: true, validations: {isEmail: true}},
+        photo_id: {type: 'integer', nullable: true, unsigned: true, references: 'photo.photo_id'},
+        company: {type: 'string', maxlength: 254, nullable: false},
+        introduction: {type: 'string', maxlength: 510, nullable: false},
+        last_login: {type: 'dateTime', nullable: true},
+        created_at: {type: 'dateTime', nullable: false},
+    },
+    vnf: {
+        vnf_id: {type: 'increments', nullable: false, primary: true},
+        vnf_name: {type: 'string', maxlength: 254, nullable: false},
+        repo_url: {type: 'string', maxlength: 254, nullable: false},
+        photo_id: {type: 'integer', nullable: true, unsigned: true, references: 'photo.photo_id'},
+        submitter_id: {type: 'integer', nullable: false, unsigned: true, references: 'user.user_id'},
+        lines_of_code: {type: 'integer', nullable: true, unsigned: true},
+        versions: {type: 'integer', nullable: true, unsigned: true},
+        no_of_developers: {type: 'integer', nullable: true, unsigned: true},
+        no_of_stars: {type: 'integer', nullable: true, unsigned: true},
+        license: {type: 'enum', nullable: false, values: ['MIT', 'GPL', 'GPL_V2', 'BSD', 'APACHE']},
+        opnfv_indicator: {type: 'enum', nullable: false, values: ['gold', 'silver', 'platinum']},
+        complexity: {type: 'enum', nullable: true, values: ['low', 'medium', 'high']},
+        activity: {type: 'enum', nullable: true, values: ['low', 'medium', 'high']},
+        last_updated: {type: 'dateTime', nullable: true},
+    },
+    tag: {
+        tag_id: {type: 'increments', nullable: false, primary: true},
+        tag_name: {type: 'string', maxlength: 150, nullable: false}
+    },
+    vnf_tags: {
+        vnf_tag_id: {type: 'increments', nullable: false, primary: true},
+        tag_id: {type: 'integer', nullable: false, unsigned: true, references: 'tag.tag_id'},
+        vnf_id: {type: 'integer', nullable: false, unsigned: true, references: 'vnf.vnf_id'},
+    },
+    vnf_contributors: {
+        vnf_contributors_id: {type: 'increments', nullable: false, primary: true},
+        user_id: {type: 'integer', nullable: false, unsigned: true, references: 'user.user_id'},
+        vnf_id: {type: 'integer', nullable: false, unsigned: true, references: 'vnf.vnf_id'},
+        created_at: {type: 'dateTime', nullable: false},
+    }
+};
+module.exports = Schema;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/package.json b/utils/test/vnfcatalogue/VNF_Catalogue/package.json
new file mode 100644 (file)
index 0000000..b7a6c23
--- /dev/null
@@ -0,0 +1,27 @@
+{
+    "name": "VNF_Catalogue",
+    "version": "0.0.1",
+    "private": true,
+    "scripts": {
+        "start": "node ./bin/www"
+    },
+    "dependencies": {
+        "body-parser": "~1.15.1",
+        "cookie-parser": "~1.4.3",
+        "debug": "~2.2.0",
+        "express": "~4.13.4",
+        "jade": "~1.11.0",
+        "morgan": "~1.7.0",
+        "serve-favicon": "~2.3.0",
+        "mysql": "*",
+        "express-validator": "*",
+        "nodemon": "*",
+        "async": "*",
+        "multer": "*",
+        "octonode": "*",
+        "bookshelf": "*",
+        "knex": "*",
+        "when": "*",
+        "lodash": "*"
+    }
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/.DS_Store b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/.DS_Store
new file mode 100644 (file)
index 0000000..2828ef6
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/.DS_Store differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/LICENSE b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/LICENSE
new file mode 100644 (file)
index 0000000..44bd03e
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2017 Materialize
+
+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/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/README.md b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/README.md
new file mode 100644 (file)
index 0000000..497b01a
--- /dev/null
@@ -0,0 +1,49 @@
+![alt tag](https://raw.github.com/dogfalo/materialize/master/images/materialize.gif)
+===========
+
+[![Travis CI](https://travis-ci.org/Dogfalo/materialize.svg?branch=master)](https://travis-ci.org/Dogfalo/materialize)[![devDependency Status](https://david-dm.org/Dogfalo/materialize/dev-status.svg)](https://david-dm.org/Dogfalo/materialize#info=devDependencies)[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Dogfalo/materialize?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+[Materialize](http://materializecss.com/), a CSS Framework based on material design
+
+### Current Version : v0.97.8
+
+## Sass Requirements:
+- Ruby Sass 3.3+, LibSass 0.6+
+
+## Supported Browsers:
+Chrome 35+, Firefox 31+, Safari 7+, IE 10+
+
+## Changelog
+- v0.97.8 (October 30th, 2016)
+  - **Refactored Modal plugin**
+  - Tabs now supported in navbar
+  - Chips data can now be reinitiailized
+  - Minor side nav fixes
+  - FAB to toolbar component added
+  - Fixed dropdown options bug
+- v0.97.7 (July 23rd, 2016)
+  - Basic horizontal cards
+  - Carousel bug fixes and new features
+  - Updated sidenav styles and new component
+  - Meteor package now supports Sass
+  - Autocomplete form component
+  - Chips jQuery plugin
+- v0.97.6 (April 1st, 2016)
+  - **Removed deprecated material icons from project**
+  - **Changed /font directory to /fonts**
+  - Datepicker and ScrollSpy now compatible with jQuery 2.2.x
+  - Responsive tables now work with empty cells
+  - Added focus states to checkboxes, switches, and radio buttons
+  - Sidenav and Modals no longer cause flicker with scrollbar
+  - Materialbox overflow and z-index issues fixed
+  - Added new option for Card actions within a Card reveal
+- v0.97.5 (December 21st, 2015)
+  - Fixed Meteor package crash
+
+
+
+## Contributing
+[Please read CONTRIBUTING.md for more information](CONTRIBUTING.md)
+
+## Testing
+We use Jasmine as our testing framework and we're trying to write a robust test suite for our components. If you want to help, [here's a starting guide on how to write tests in Jasmine](https://docs.google.com/document/d/1dVM6qGt_b_y9RRhr9X7oZfFydaJIEqB9CT7yekv-4XE/edit?usp=sharing)
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/css/materialize.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/css/materialize.css
new file mode 100644 (file)
index 0000000..27de210
--- /dev/null
@@ -0,0 +1,8582 @@
+/*!
+ * Materialize v0.98.0 (http://materializecss.com)
+ * Copyright 2014-2015 Materialize
+ * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE)
+ */
+.materialize-red {
+  background-color: #e51c23 !important;
+}
+
+.materialize-red-text {
+  color: #e51c23 !important;
+}
+
+.materialize-red.lighten-5 {
+  background-color: #fdeaeb !important;
+}
+
+.materialize-red-text.text-lighten-5 {
+  color: #fdeaeb !important;
+}
+
+.materialize-red.lighten-4 {
+  background-color: #f8c1c3 !important;
+}
+
+.materialize-red-text.text-lighten-4 {
+  color: #f8c1c3 !important;
+}
+
+.materialize-red.lighten-3 {
+  background-color: #f3989b !important;
+}
+
+.materialize-red-text.text-lighten-3 {
+  color: #f3989b !important;
+}
+
+.materialize-red.lighten-2 {
+  background-color: #ee6e73 !important;
+}
+
+.materialize-red-text.text-lighten-2 {
+  color: #ee6e73 !important;
+}
+
+.materialize-red.lighten-1 {
+  background-color: #ea454b !important;
+}
+
+.materialize-red-text.text-lighten-1 {
+  color: #ea454b !important;
+}
+
+.materialize-red.darken-1 {
+  background-color: #d0181e !important;
+}
+
+.materialize-red-text.text-darken-1 {
+  color: #d0181e !important;
+}
+
+.materialize-red.darken-2 {
+  background-color: #b9151b !important;
+}
+
+.materialize-red-text.text-darken-2 {
+  color: #b9151b !important;
+}
+
+.materialize-red.darken-3 {
+  background-color: #a21318 !important;
+}
+
+.materialize-red-text.text-darken-3 {
+  color: #a21318 !important;
+}
+
+.materialize-red.darken-4 {
+  background-color: #8b1014 !important;
+}
+
+.materialize-red-text.text-darken-4 {
+  color: #8b1014 !important;
+}
+
+.red {
+  background-color: #F44336 !important;
+}
+
+.red-text {
+  color: #F44336 !important;
+}
+
+.red.lighten-5 {
+  background-color: #FFEBEE !important;
+}
+
+.red-text.text-lighten-5 {
+  color: #FFEBEE !important;
+}
+
+.red.lighten-4 {
+  background-color: #FFCDD2 !important;
+}
+
+.red-text.text-lighten-4 {
+  color: #FFCDD2 !important;
+}
+
+.red.lighten-3 {
+  background-color: #EF9A9A !important;
+}
+
+.red-text.text-lighten-3 {
+  color: #EF9A9A !important;
+}
+
+.red.lighten-2 {
+  background-color: #E57373 !important;
+}
+
+.red-text.text-lighten-2 {
+  color: #E57373 !important;
+}
+
+.red.lighten-1 {
+  background-color: #EF5350 !important;
+}
+
+.red-text.text-lighten-1 {
+  color: #EF5350 !important;
+}
+
+.red.darken-1 {
+  background-color: #E53935 !important;
+}
+
+.red-text.text-darken-1 {
+  color: #E53935 !important;
+}
+
+.red.darken-2 {
+  background-color: #D32F2F !important;
+}
+
+.red-text.text-darken-2 {
+  color: #D32F2F !important;
+}
+
+.red.darken-3 {
+  background-color: #C62828 !important;
+}
+
+.red-text.text-darken-3 {
+  color: #C62828 !important;
+}
+
+.red.darken-4 {
+  background-color: #B71C1C !important;
+}
+
+.red-text.text-darken-4 {
+  color: #B71C1C !important;
+}
+
+.red.accent-1 {
+  background-color: #FF8A80 !important;
+}
+
+.red-text.text-accent-1 {
+  color: #FF8A80 !important;
+}
+
+.red.accent-2 {
+  background-color: #FF5252 !important;
+}
+
+.red-text.text-accent-2 {
+  color: #FF5252 !important;
+}
+
+.red.accent-3 {
+  background-color: #FF1744 !important;
+}
+
+.red-text.text-accent-3 {
+  color: #FF1744 !important;
+}
+
+.red.accent-4 {
+  background-color: #D50000 !important;
+}
+
+.red-text.text-accent-4 {
+  color: #D50000 !important;
+}
+
+.pink {
+  background-color: #e91e63 !important;
+}
+
+.pink-text {
+  color: #e91e63 !important;
+}
+
+.pink.lighten-5 {
+  background-color: #fce4ec !important;
+}
+
+.pink-text.text-lighten-5 {
+  color: #fce4ec !important;
+}
+
+.pink.lighten-4 {
+  background-color: #f8bbd0 !important;
+}
+
+.pink-text.text-lighten-4 {
+  color: #f8bbd0 !important;
+}
+
+.pink.lighten-3 {
+  background-color: #f48fb1 !important;
+}
+
+.pink-text.text-lighten-3 {
+  color: #f48fb1 !important;
+}
+
+.pink.lighten-2 {
+  background-color: #f06292 !important;
+}
+
+.pink-text.text-lighten-2 {
+  color: #f06292 !important;
+}
+
+.pink.lighten-1 {
+  background-color: #ec407a !important;
+}
+
+.pink-text.text-lighten-1 {
+  color: #ec407a !important;
+}
+
+.pink.darken-1 {
+  background-color: #d81b60 !important;
+}
+
+.pink-text.text-darken-1 {
+  color: #d81b60 !important;
+}
+
+.pink.darken-2 {
+  background-color: #c2185b !important;
+}
+
+.pink-text.text-darken-2 {
+  color: #c2185b !important;
+}
+
+.pink.darken-3 {
+  background-color: #ad1457 !important;
+}
+
+.pink-text.text-darken-3 {
+  color: #ad1457 !important;
+}
+
+.pink.darken-4 {
+  background-color: #880e4f !important;
+}
+
+.pink-text.text-darken-4 {
+  color: #880e4f !important;
+}
+
+.pink.accent-1 {
+  background-color: #ff80ab !important;
+}
+
+.pink-text.text-accent-1 {
+  color: #ff80ab !important;
+}
+
+.pink.accent-2 {
+  background-color: #ff4081 !important;
+}
+
+.pink-text.text-accent-2 {
+  color: #ff4081 !important;
+}
+
+.pink.accent-3 {
+  background-color: #f50057 !important;
+}
+
+.pink-text.text-accent-3 {
+  color: #f50057 !important;
+}
+
+.pink.accent-4 {
+  background-color: #c51162 !important;
+}
+
+.pink-text.text-accent-4 {
+  color: #c51162 !important;
+}
+
+.purple {
+  background-color: #9c27b0 !important;
+}
+
+.purple-text {
+  color: #9c27b0 !important;
+}
+
+.purple.lighten-5 {
+  background-color: #f3e5f5 !important;
+}
+
+.purple-text.text-lighten-5 {
+  color: #f3e5f5 !important;
+}
+
+.purple.lighten-4 {
+  background-color: #e1bee7 !important;
+}
+
+.purple-text.text-lighten-4 {
+  color: #e1bee7 !important;
+}
+
+.purple.lighten-3 {
+  background-color: #ce93d8 !important;
+}
+
+.purple-text.text-lighten-3 {
+  color: #ce93d8 !important;
+}
+
+.purple.lighten-2 {
+  background-color: #ba68c8 !important;
+}
+
+.purple-text.text-lighten-2 {
+  color: #ba68c8 !important;
+}
+
+.purple.lighten-1 {
+  background-color: #ab47bc !important;
+}
+
+.purple-text.text-lighten-1 {
+  color: #ab47bc !important;
+}
+
+.purple.darken-1 {
+  background-color: #8e24aa !important;
+}
+
+.purple-text.text-darken-1 {
+  color: #8e24aa !important;
+}
+
+.purple.darken-2 {
+  background-color: #7b1fa2 !important;
+}
+
+.purple-text.text-darken-2 {
+  color: #7b1fa2 !important;
+}
+
+.purple.darken-3 {
+  background-color: #6a1b9a !important;
+}
+
+.purple-text.text-darken-3 {
+  color: #6a1b9a !important;
+}
+
+.purple.darken-4 {
+  background-color: #4a148c !important;
+}
+
+.purple-text.text-darken-4 {
+  color: #4a148c !important;
+}
+
+.purple.accent-1 {
+  background-color: #ea80fc !important;
+}
+
+.purple-text.text-accent-1 {
+  color: #ea80fc !important;
+}
+
+.purple.accent-2 {
+  background-color: #e040fb !important;
+}
+
+.purple-text.text-accent-2 {
+  color: #e040fb !important;
+}
+
+.purple.accent-3 {
+  background-color: #d500f9 !important;
+}
+
+.purple-text.text-accent-3 {
+  color: #d500f9 !important;
+}
+
+.purple.accent-4 {
+  background-color: #aa00ff !important;
+}
+
+.purple-text.text-accent-4 {
+  color: #aa00ff !important;
+}
+
+.deep-purple {
+  background-color: #673ab7 !important;
+}
+
+.deep-purple-text {
+  color: #673ab7 !important;
+}
+
+.deep-purple.lighten-5 {
+  background-color: #ede7f6 !important;
+}
+
+.deep-purple-text.text-lighten-5 {
+  color: #ede7f6 !important;
+}
+
+.deep-purple.lighten-4 {
+  background-color: #d1c4e9 !important;
+}
+
+.deep-purple-text.text-lighten-4 {
+  color: #d1c4e9 !important;
+}
+
+.deep-purple.lighten-3 {
+  background-color: #b39ddb !important;
+}
+
+.deep-purple-text.text-lighten-3 {
+  color: #b39ddb !important;
+}
+
+.deep-purple.lighten-2 {
+  background-color: #9575cd !important;
+}
+
+.deep-purple-text.text-lighten-2 {
+  color: #9575cd !important;
+}
+
+.deep-purple.lighten-1 {
+  background-color: #7e57c2 !important;
+}
+
+.deep-purple-text.text-lighten-1 {
+  color: #7e57c2 !important;
+}
+
+.deep-purple.darken-1 {
+  background-color: #5e35b1 !important;
+}
+
+.deep-purple-text.text-darken-1 {
+  color: #5e35b1 !important;
+}
+
+.deep-purple.darken-2 {
+  background-color: #512da8 !important;
+}
+
+.deep-purple-text.text-darken-2 {
+  color: #512da8 !important;
+}
+
+.deep-purple.darken-3 {
+  background-color: #4527a0 !important;
+}
+
+.deep-purple-text.text-darken-3 {
+  color: #4527a0 !important;
+}
+
+.deep-purple.darken-4 {
+  background-color: #311b92 !important;
+}
+
+.deep-purple-text.text-darken-4 {
+  color: #311b92 !important;
+}
+
+.deep-purple.accent-1 {
+  background-color: #b388ff !important;
+}
+
+.deep-purple-text.text-accent-1 {
+  color: #b388ff !important;
+}
+
+.deep-purple.accent-2 {
+  background-color: #7c4dff !important;
+}
+
+.deep-purple-text.text-accent-2 {
+  color: #7c4dff !important;
+}
+
+.deep-purple.accent-3 {
+  background-color: #651fff !important;
+}
+
+.deep-purple-text.text-accent-3 {
+  color: #651fff !important;
+}
+
+.deep-purple.accent-4 {
+  background-color: #6200ea !important;
+}
+
+.deep-purple-text.text-accent-4 {
+  color: #6200ea !important;
+}
+
+.indigo {
+  background-color: #3f51b5 !important;
+}
+
+.indigo-text {
+  color: #3f51b5 !important;
+}
+
+.indigo.lighten-5 {
+  background-color: #e8eaf6 !important;
+}
+
+.indigo-text.text-lighten-5 {
+  color: #e8eaf6 !important;
+}
+
+.indigo.lighten-4 {
+  background-color: #c5cae9 !important;
+}
+
+.indigo-text.text-lighten-4 {
+  color: #c5cae9 !important;
+}
+
+.indigo.lighten-3 {
+  background-color: #9fa8da !important;
+}
+
+.indigo-text.text-lighten-3 {
+  color: #9fa8da !important;
+}
+
+.indigo.lighten-2 {
+  background-color: #7986cb !important;
+}
+
+.indigo-text.text-lighten-2 {
+  color: #7986cb !important;
+}
+
+.indigo.lighten-1 {
+  background-color: #5c6bc0 !important;
+}
+
+.indigo-text.text-lighten-1 {
+  color: #5c6bc0 !important;
+}
+
+.indigo.darken-1 {
+  background-color: #3949ab !important;
+}
+
+.indigo-text.text-darken-1 {
+  color: #3949ab !important;
+}
+
+.indigo.darken-2 {
+  background-color: #303f9f !important;
+}
+
+.indigo-text.text-darken-2 {
+  color: #303f9f !important;
+}
+
+.indigo.darken-3 {
+  background-color: #283593 !important;
+}
+
+.indigo-text.text-darken-3 {
+  color: #283593 !important;
+}
+
+.indigo.darken-4 {
+  background-color: #1a237e !important;
+}
+
+.indigo-text.text-darken-4 {
+  color: #1a237e !important;
+}
+
+.indigo.accent-1 {
+  background-color: #8c9eff !important;
+}
+
+.indigo-text.text-accent-1 {
+  color: #8c9eff !important;
+}
+
+.indigo.accent-2 {
+  background-color: #536dfe !important;
+}
+
+.indigo-text.text-accent-2 {
+  color: #536dfe !important;
+}
+
+.indigo.accent-3 {
+  background-color: #3d5afe !important;
+}
+
+.indigo-text.text-accent-3 {
+  color: #3d5afe !important;
+}
+
+.indigo.accent-4 {
+  background-color: #304ffe !important;
+}
+
+.indigo-text.text-accent-4 {
+  color: #304ffe !important;
+}
+
+.blue {
+  background-color: #2196F3 !important;
+}
+
+.blue-text {
+  color: #2196F3 !important;
+}
+
+.blue.lighten-5 {
+  background-color: #E3F2FD !important;
+}
+
+.blue-text.text-lighten-5 {
+  color: #E3F2FD !important;
+}
+
+.blue.lighten-4 {
+  background-color: #BBDEFB !important;
+}
+
+.blue-text.text-lighten-4 {
+  color: #BBDEFB !important;
+}
+
+.blue.lighten-3 {
+  background-color: #90CAF9 !important;
+}
+
+.blue-text.text-lighten-3 {
+  color: #90CAF9 !important;
+}
+
+.blue.lighten-2 {
+  background-color: #64B5F6 !important;
+}
+
+.blue-text.text-lighten-2 {
+  color: #64B5F6 !important;
+}
+
+.blue.lighten-1 {
+  background-color: #42A5F5 !important;
+}
+
+.blue-text.text-lighten-1 {
+  color: #42A5F5 !important;
+}
+
+.blue.darken-1 {
+  background-color: #1E88E5 !important;
+}
+
+.blue-text.text-darken-1 {
+  color: #1E88E5 !important;
+}
+
+.blue.darken-2 {
+  background-color: #1976D2 !important;
+}
+
+.blue-text.text-darken-2 {
+  color: #1976D2 !important;
+}
+
+.blue.darken-3 {
+  background-color: #1565C0 !important;
+}
+
+.blue-text.text-darken-3 {
+  color: #1565C0 !important;
+}
+
+.blue.darken-4 {
+  background-color: #0D47A1 !important;
+}
+
+.blue-text.text-darken-4 {
+  color: #0D47A1 !important;
+}
+
+.blue.accent-1 {
+  background-color: #82B1FF !important;
+}
+
+.blue-text.text-accent-1 {
+  color: #82B1FF !important;
+}
+
+.blue.accent-2 {
+  background-color: #448AFF !important;
+}
+
+.blue-text.text-accent-2 {
+  color: #448AFF !important;
+}
+
+.blue.accent-3 {
+  background-color: #2979FF !important;
+}
+
+.blue-text.text-accent-3 {
+  color: #2979FF !important;
+}
+
+.blue.accent-4 {
+  background-color: #2962FF !important;
+}
+
+.blue-text.text-accent-4 {
+  color: #2962FF !important;
+}
+
+.light-blue {
+  background-color: #03a9f4 !important;
+}
+
+.light-blue-text {
+  color: #03a9f4 !important;
+}
+
+.light-blue.lighten-5 {
+  background-color: #e1f5fe !important;
+}
+
+.light-blue-text.text-lighten-5 {
+  color: #e1f5fe !important;
+}
+
+.light-blue.lighten-4 {
+  background-color: #b3e5fc !important;
+}
+
+.light-blue-text.text-lighten-4 {
+  color: #b3e5fc !important;
+}
+
+.light-blue.lighten-3 {
+  background-color: #81d4fa !important;
+}
+
+.light-blue-text.text-lighten-3 {
+  color: #81d4fa !important;
+}
+
+.light-blue.lighten-2 {
+  background-color: #4fc3f7 !important;
+}
+
+.light-blue-text.text-lighten-2 {
+  color: #4fc3f7 !important;
+}
+
+.light-blue.lighten-1 {
+  background-color: #29b6f6 !important;
+}
+
+.light-blue-text.text-lighten-1 {
+  color: #29b6f6 !important;
+}
+
+.light-blue.darken-1 {
+  background-color: #039be5 !important;
+}
+
+.light-blue-text.text-darken-1 {
+  color: #039be5 !important;
+}
+
+.light-blue.darken-2 {
+  background-color: #0288d1 !important;
+}
+
+.light-blue-text.text-darken-2 {
+  color: #0288d1 !important;
+}
+
+.light-blue.darken-3 {
+  background-color: #0277bd !important;
+}
+
+.light-blue-text.text-darken-3 {
+  color: #0277bd !important;
+}
+
+.light-blue.darken-4 {
+  background-color: #01579b !important;
+}
+
+.light-blue-text.text-darken-4 {
+  color: #01579b !important;
+}
+
+.light-blue.accent-1 {
+  background-color: #80d8ff !important;
+}
+
+.light-blue-text.text-accent-1 {
+  color: #80d8ff !important;
+}
+
+.light-blue.accent-2 {
+  background-color: #40c4ff !important;
+}
+
+.light-blue-text.text-accent-2 {
+  color: #40c4ff !important;
+}
+
+.light-blue.accent-3 {
+  background-color: #00b0ff !important;
+}
+
+.light-blue-text.text-accent-3 {
+  color: #00b0ff !important;
+}
+
+.light-blue.accent-4 {
+  background-color: #0091ea !important;
+}
+
+.light-blue-text.text-accent-4 {
+  color: #0091ea !important;
+}
+
+.cyan {
+  background-color: #00bcd4 !important;
+}
+
+.cyan-text {
+  color: #00bcd4 !important;
+}
+
+.cyan.lighten-5 {
+  background-color: #e0f7fa !important;
+}
+
+.cyan-text.text-lighten-5 {
+  color: #e0f7fa !important;
+}
+
+.cyan.lighten-4 {
+  background-color: #b2ebf2 !important;
+}
+
+.cyan-text.text-lighten-4 {
+  color: #b2ebf2 !important;
+}
+
+.cyan.lighten-3 {
+  background-color: #80deea !important;
+}
+
+.cyan-text.text-lighten-3 {
+  color: #80deea !important;
+}
+
+.cyan.lighten-2 {
+  background-color: #4dd0e1 !important;
+}
+
+.cyan-text.text-lighten-2 {
+  color: #4dd0e1 !important;
+}
+
+.cyan.lighten-1 {
+  background-color: #26c6da !important;
+}
+
+.cyan-text.text-lighten-1 {
+  color: #26c6da !important;
+}
+
+.cyan.darken-1 {
+  background-color: #00acc1 !important;
+}
+
+.cyan-text.text-darken-1 {
+  color: #00acc1 !important;
+}
+
+.cyan.darken-2 {
+  background-color: #0097a7 !important;
+}
+
+.cyan-text.text-darken-2 {
+  color: #0097a7 !important;
+}
+
+.cyan.darken-3 {
+  background-color: #00838f !important;
+}
+
+.cyan-text.text-darken-3 {
+  color: #00838f !important;
+}
+
+.cyan.darken-4 {
+  background-color: #006064 !important;
+}
+
+.cyan-text.text-darken-4 {
+  color: #006064 !important;
+}
+
+.cyan.accent-1 {
+  background-color: #84ffff !important;
+}
+
+.cyan-text.text-accent-1 {
+  color: #84ffff !important;
+}
+
+.cyan.accent-2 {
+  background-color: #18ffff !important;
+}
+
+.cyan-text.text-accent-2 {
+  color: #18ffff !important;
+}
+
+.cyan.accent-3 {
+  background-color: #00e5ff !important;
+}
+
+.cyan-text.text-accent-3 {
+  color: #00e5ff !important;
+}
+
+.cyan.accent-4 {
+  background-color: #00b8d4 !important;
+}
+
+.cyan-text.text-accent-4 {
+  color: #00b8d4 !important;
+}
+
+.teal {
+  background-color: #009688 !important;
+}
+
+.teal-text {
+  color: #009688 !important;
+}
+
+.teal.lighten-5 {
+  background-color: #e0f2f1 !important;
+}
+
+.teal-text.text-lighten-5 {
+  color: #e0f2f1 !important;
+}
+
+.teal.lighten-4 {
+  background-color: #b2dfdb !important;
+}
+
+.teal-text.text-lighten-4 {
+  color: #b2dfdb !important;
+}
+
+.teal.lighten-3 {
+  background-color: #80cbc4 !important;
+}
+
+.teal-text.text-lighten-3 {
+  color: #80cbc4 !important;
+}
+
+.teal.lighten-2 {
+  background-color: #4db6ac !important;
+}
+
+.teal-text.text-lighten-2 {
+  color: #4db6ac !important;
+}
+
+.teal.lighten-1 {
+  background-color: #26a69a !important;
+}
+
+.teal-text.text-lighten-1 {
+  color: #26a69a !important;
+}
+
+.teal.darken-1 {
+  background-color: #00897b !important;
+}
+
+.teal-text.text-darken-1 {
+  color: #00897b !important;
+}
+
+.teal.darken-2 {
+  background-color: #00796b !important;
+}
+
+.teal-text.text-darken-2 {
+  color: #00796b !important;
+}
+
+.teal.darken-3 {
+  background-color: #00695c !important;
+}
+
+.teal-text.text-darken-3 {
+  color: #00695c !important;
+}
+
+.teal.darken-4 {
+  background-color: #004d40 !important;
+}
+
+.teal-text.text-darken-4 {
+  color: #004d40 !important;
+}
+
+.teal.accent-1 {
+  background-color: #a7ffeb !important;
+}
+
+.teal-text.text-accent-1 {
+  color: #a7ffeb !important;
+}
+
+.teal.accent-2 {
+  background-color: #64ffda !important;
+}
+
+.teal-text.text-accent-2 {
+  color: #64ffda !important;
+}
+
+.teal.accent-3 {
+  background-color: #1de9b6 !important;
+}
+
+.teal-text.text-accent-3 {
+  color: #1de9b6 !important;
+}
+
+.teal.accent-4 {
+  background-color: #00bfa5 !important;
+}
+
+.teal-text.text-accent-4 {
+  color: #00bfa5 !important;
+}
+
+.green {
+  background-color: #4CAF50 !important;
+}
+
+.green-text {
+  color: #4CAF50 !important;
+}
+
+.green.lighten-5 {
+  background-color: #E8F5E9 !important;
+}
+
+.green-text.text-lighten-5 {
+  color: #E8F5E9 !important;
+}
+
+.green.lighten-4 {
+  background-color: #C8E6C9 !important;
+}
+
+.green-text.text-lighten-4 {
+  color: #C8E6C9 !important;
+}
+
+.green.lighten-3 {
+  background-color: #A5D6A7 !important;
+}
+
+.green-text.text-lighten-3 {
+  color: #A5D6A7 !important;
+}
+
+.green.lighten-2 {
+  background-color: #81C784 !important;
+}
+
+.green-text.text-lighten-2 {
+  color: #81C784 !important;
+}
+
+.green.lighten-1 {
+  background-color: #66BB6A !important;
+}
+
+.green-text.text-lighten-1 {
+  color: #66BB6A !important;
+}
+
+.green.darken-1 {
+  background-color: #43A047 !important;
+}
+
+.green-text.text-darken-1 {
+  color: #43A047 !important;
+}
+
+.green.darken-2 {
+  background-color: #388E3C !important;
+}
+
+.green-text.text-darken-2 {
+  color: #388E3C !important;
+}
+
+.green.darken-3 {
+  background-color: #2E7D32 !important;
+}
+
+.green-text.text-darken-3 {
+  color: #2E7D32 !important;
+}
+
+.green.darken-4 {
+  background-color: #1B5E20 !important;
+}
+
+.green-text.text-darken-4 {
+  color: #1B5E20 !important;
+}
+
+.green.accent-1 {
+  background-color: #B9F6CA !important;
+}
+
+.green-text.text-accent-1 {
+  color: #B9F6CA !important;
+}
+
+.green.accent-2 {
+  background-color: #69F0AE !important;
+}
+
+.green-text.text-accent-2 {
+  color: #69F0AE !important;
+}
+
+.green.accent-3 {
+  background-color: #00E676 !important;
+}
+
+.green-text.text-accent-3 {
+  color: #00E676 !important;
+}
+
+.green.accent-4 {
+  background-color: #00C853 !important;
+}
+
+.green-text.text-accent-4 {
+  color: #00C853 !important;
+}
+
+.light-green {
+  background-color: #8bc34a !important;
+}
+
+.light-green-text {
+  color: #8bc34a !important;
+}
+
+.light-green.lighten-5 {
+  background-color: #f1f8e9 !important;
+}
+
+.light-green-text.text-lighten-5 {
+  color: #f1f8e9 !important;
+}
+
+.light-green.lighten-4 {
+  background-color: #dcedc8 !important;
+}
+
+.light-green-text.text-lighten-4 {
+  color: #dcedc8 !important;
+}
+
+.light-green.lighten-3 {
+  background-color: #c5e1a5 !important;
+}
+
+.light-green-text.text-lighten-3 {
+  color: #c5e1a5 !important;
+}
+
+.light-green.lighten-2 {
+  background-color: #aed581 !important;
+}
+
+.light-green-text.text-lighten-2 {
+  color: #aed581 !important;
+}
+
+.light-green.lighten-1 {
+  background-color: #9ccc65 !important;
+}
+
+.light-green-text.text-lighten-1 {
+  color: #9ccc65 !important;
+}
+
+.light-green.darken-1 {
+  background-color: #7cb342 !important;
+}
+
+.light-green-text.text-darken-1 {
+  color: #7cb342 !important;
+}
+
+.light-green.darken-2 {
+  background-color: #689f38 !important;
+}
+
+.light-green-text.text-darken-2 {
+  color: #689f38 !important;
+}
+
+.light-green.darken-3 {
+  background-color: #558b2f !important;
+}
+
+.light-green-text.text-darken-3 {
+  color: #558b2f !important;
+}
+
+.light-green.darken-4 {
+  background-color: #33691e !important;
+}
+
+.light-green-text.text-darken-4 {
+  color: #33691e !important;
+}
+
+.light-green.accent-1 {
+  background-color: #ccff90 !important;
+}
+
+.light-green-text.text-accent-1 {
+  color: #ccff90 !important;
+}
+
+.light-green.accent-2 {
+  background-color: #b2ff59 !important;
+}
+
+.light-green-text.text-accent-2 {
+  color: #b2ff59 !important;
+}
+
+.light-green.accent-3 {
+  background-color: #76ff03 !important;
+}
+
+.light-green-text.text-accent-3 {
+  color: #76ff03 !important;
+}
+
+.light-green.accent-4 {
+  background-color: #64dd17 !important;
+}
+
+.light-green-text.text-accent-4 {
+  color: #64dd17 !important;
+}
+
+.lime {
+  background-color: #cddc39 !important;
+}
+
+.lime-text {
+  color: #cddc39 !important;
+}
+
+.lime.lighten-5 {
+  background-color: #f9fbe7 !important;
+}
+
+.lime-text.text-lighten-5 {
+  color: #f9fbe7 !important;
+}
+
+.lime.lighten-4 {
+  background-color: #f0f4c3 !important;
+}
+
+.lime-text.text-lighten-4 {
+  color: #f0f4c3 !important;
+}
+
+.lime.lighten-3 {
+  background-color: #e6ee9c !important;
+}
+
+.lime-text.text-lighten-3 {
+  color: #e6ee9c !important;
+}
+
+.lime.lighten-2 {
+  background-color: #dce775 !important;
+}
+
+.lime-text.text-lighten-2 {
+  color: #dce775 !important;
+}
+
+.lime.lighten-1 {
+  background-color: #d4e157 !important;
+}
+
+.lime-text.text-lighten-1 {
+  color: #d4e157 !important;
+}
+
+.lime.darken-1 {
+  background-color: #c0ca33 !important;
+}
+
+.lime-text.text-darken-1 {
+  color: #c0ca33 !important;
+}
+
+.lime.darken-2 {
+  background-color: #afb42b !important;
+}
+
+.lime-text.text-darken-2 {
+  color: #afb42b !important;
+}
+
+.lime.darken-3 {
+  background-color: #9e9d24 !important;
+}
+
+.lime-text.text-darken-3 {
+  color: #9e9d24 !important;
+}
+
+.lime.darken-4 {
+  background-color: #827717 !important;
+}
+
+.lime-text.text-darken-4 {
+  color: #827717 !important;
+}
+
+.lime.accent-1 {
+  background-color: #f4ff81 !important;
+}
+
+.lime-text.text-accent-1 {
+  color: #f4ff81 !important;
+}
+
+.lime.accent-2 {
+  background-color: #eeff41 !important;
+}
+
+.lime-text.text-accent-2 {
+  color: #eeff41 !important;
+}
+
+.lime.accent-3 {
+  background-color: #c6ff00 !important;
+}
+
+.lime-text.text-accent-3 {
+  color: #c6ff00 !important;
+}
+
+.lime.accent-4 {
+  background-color: #aeea00 !important;
+}
+
+.lime-text.text-accent-4 {
+  color: #aeea00 !important;
+}
+
+.yellow {
+  background-color: #ffeb3b !important;
+}
+
+.yellow-text {
+  color: #ffeb3b !important;
+}
+
+.yellow.lighten-5 {
+  background-color: #fffde7 !important;
+}
+
+.yellow-text.text-lighten-5 {
+  color: #fffde7 !important;
+}
+
+.yellow.lighten-4 {
+  background-color: #fff9c4 !important;
+}
+
+.yellow-text.text-lighten-4 {
+  color: #fff9c4 !important;
+}
+
+.yellow.lighten-3 {
+  background-color: #fff59d !important;
+}
+
+.yellow-text.text-lighten-3 {
+  color: #fff59d !important;
+}
+
+.yellow.lighten-2 {
+  background-color: #fff176 !important;
+}
+
+.yellow-text.text-lighten-2 {
+  color: #fff176 !important;
+}
+
+.yellow.lighten-1 {
+  background-color: #ffee58 !important;
+}
+
+.yellow-text.text-lighten-1 {
+  color: #ffee58 !important;
+}
+
+.yellow.darken-1 {
+  background-color: #fdd835 !important;
+}
+
+.yellow-text.text-darken-1 {
+  color: #fdd835 !important;
+}
+
+.yellow.darken-2 {
+  background-color: #fbc02d !important;
+}
+
+.yellow-text.text-darken-2 {
+  color: #fbc02d !important;
+}
+
+.yellow.darken-3 {
+  background-color: #f9a825 !important;
+}
+
+.yellow-text.text-darken-3 {
+  color: #f9a825 !important;
+}
+
+.yellow.darken-4 {
+  background-color: #f57f17 !important;
+}
+
+.yellow-text.text-darken-4 {
+  color: #f57f17 !important;
+}
+
+.yellow.accent-1 {
+  background-color: #ffff8d !important;
+}
+
+.yellow-text.text-accent-1 {
+  color: #ffff8d !important;
+}
+
+.yellow.accent-2 {
+  background-color: #ffff00 !important;
+}
+
+.yellow-text.text-accent-2 {
+  color: #ffff00 !important;
+}
+
+.yellow.accent-3 {
+  background-color: #ffea00 !important;
+}
+
+.yellow-text.text-accent-3 {
+  color: #ffea00 !important;
+}
+
+.yellow.accent-4 {
+  background-color: #ffd600 !important;
+}
+
+.yellow-text.text-accent-4 {
+  color: #ffd600 !important;
+}
+
+.amber {
+  background-color: #ffc107 !important;
+}
+
+.amber-text {
+  color: #ffc107 !important;
+}
+
+.amber.lighten-5 {
+  background-color: #fff8e1 !important;
+}
+
+.amber-text.text-lighten-5 {
+  color: #fff8e1 !important;
+}
+
+.amber.lighten-4 {
+  background-color: #ffecb3 !important;
+}
+
+.amber-text.text-lighten-4 {
+  color: #ffecb3 !important;
+}
+
+.amber.lighten-3 {
+  background-color: #ffe082 !important;
+}
+
+.amber-text.text-lighten-3 {
+  color: #ffe082 !important;
+}
+
+.amber.lighten-2 {
+  background-color: #ffd54f !important;
+}
+
+.amber-text.text-lighten-2 {
+  color: #ffd54f !important;
+}
+
+.amber.lighten-1 {
+  background-color: #ffca28 !important;
+}
+
+.amber-text.text-lighten-1 {
+  color: #ffca28 !important;
+}
+
+.amber.darken-1 {
+  background-color: #ffb300 !important;
+}
+
+.amber-text.text-darken-1 {
+  color: #ffb300 !important;
+}
+
+.amber.darken-2 {
+  background-color: #ffa000 !important;
+}
+
+.amber-text.text-darken-2 {
+  color: #ffa000 !important;
+}
+
+.amber.darken-3 {
+  background-color: #ff8f00 !important;
+}
+
+.amber-text.text-darken-3 {
+  color: #ff8f00 !important;
+}
+
+.amber.darken-4 {
+  background-color: #ff6f00 !important;
+}
+
+.amber-text.text-darken-4 {
+  color: #ff6f00 !important;
+}
+
+.amber.accent-1 {
+  background-color: #ffe57f !important;
+}
+
+.amber-text.text-accent-1 {
+  color: #ffe57f !important;
+}
+
+.amber.accent-2 {
+  background-color: #ffd740 !important;
+}
+
+.amber-text.text-accent-2 {
+  color: #ffd740 !important;
+}
+
+.amber.accent-3 {
+  background-color: #ffc400 !important;
+}
+
+.amber-text.text-accent-3 {
+  color: #ffc400 !important;
+}
+
+.amber.accent-4 {
+  background-color: #ffab00 !important;
+}
+
+.amber-text.text-accent-4 {
+  color: #ffab00 !important;
+}
+
+.orange {
+  background-color: #ff9800 !important;
+}
+
+.orange-text {
+  color: #ff9800 !important;
+}
+
+.orange.lighten-5 {
+  background-color: #fff3e0 !important;
+}
+
+.orange-text.text-lighten-5 {
+  color: #fff3e0 !important;
+}
+
+.orange.lighten-4 {
+  background-color: #ffe0b2 !important;
+}
+
+.orange-text.text-lighten-4 {
+  color: #ffe0b2 !important;
+}
+
+.orange.lighten-3 {
+  background-color: #ffcc80 !important;
+}
+
+.orange-text.text-lighten-3 {
+  color: #ffcc80 !important;
+}
+
+.orange.lighten-2 {
+  background-color: #ffb74d !important;
+}
+
+.orange-text.text-lighten-2 {
+  color: #ffb74d !important;
+}
+
+.orange.lighten-1 {
+  background-color: #ffa726 !important;
+}
+
+.orange-text.text-lighten-1 {
+  color: #ffa726 !important;
+}
+
+.orange.darken-1 {
+  background-color: #fb8c00 !important;
+}
+
+.orange-text.text-darken-1 {
+  color: #fb8c00 !important;
+}
+
+.orange.darken-2 {
+  background-color: #f57c00 !important;
+}
+
+.orange-text.text-darken-2 {
+  color: #f57c00 !important;
+}
+
+.orange.darken-3 {
+  background-color: #ef6c00 !important;
+}
+
+.orange-text.text-darken-3 {
+  color: #ef6c00 !important;
+}
+
+.orange.darken-4 {
+  background-color: #e65100 !important;
+}
+
+.orange-text.text-darken-4 {
+  color: #e65100 !important;
+}
+
+.orange.accent-1 {
+  background-color: #ffd180 !important;
+}
+
+.orange-text.text-accent-1 {
+  color: #ffd180 !important;
+}
+
+.orange.accent-2 {
+  background-color: #ffab40 !important;
+}
+
+.orange-text.text-accent-2 {
+  color: #ffab40 !important;
+}
+
+.orange.accent-3 {
+  background-color: #ff9100 !important;
+}
+
+.orange-text.text-accent-3 {
+  color: #ff9100 !important;
+}
+
+.orange.accent-4 {
+  background-color: #ff6d00 !important;
+}
+
+.orange-text.text-accent-4 {
+  color: #ff6d00 !important;
+}
+
+.deep-orange {
+  background-color: #ff5722 !important;
+}
+
+.deep-orange-text {
+  color: #ff5722 !important;
+}
+
+.deep-orange.lighten-5 {
+  background-color: #fbe9e7 !important;
+}
+
+.deep-orange-text.text-lighten-5 {
+  color: #fbe9e7 !important;
+}
+
+.deep-orange.lighten-4 {
+  background-color: #ffccbc !important;
+}
+
+.deep-orange-text.text-lighten-4 {
+  color: #ffccbc !important;
+}
+
+.deep-orange.lighten-3 {
+  background-color: #ffab91 !important;
+}
+
+.deep-orange-text.text-lighten-3 {
+  color: #ffab91 !important;
+}
+
+.deep-orange.lighten-2 {
+  background-color: #ff8a65 !important;
+}
+
+.deep-orange-text.text-lighten-2 {
+  color: #ff8a65 !important;
+}
+
+.deep-orange.lighten-1 {
+  background-color: #ff7043 !important;
+}
+
+.deep-orange-text.text-lighten-1 {
+  color: #ff7043 !important;
+}
+
+.deep-orange.darken-1 {
+  background-color: #f4511e !important;
+}
+
+.deep-orange-text.text-darken-1 {
+  color: #f4511e !important;
+}
+
+.deep-orange.darken-2 {
+  background-color: #e64a19 !important;
+}
+
+.deep-orange-text.text-darken-2 {
+  color: #e64a19 !important;
+}
+
+.deep-orange.darken-3 {
+  background-color: #d84315 !important;
+}
+
+.deep-orange-text.text-darken-3 {
+  color: #d84315 !important;
+}
+
+.deep-orange.darken-4 {
+  background-color: #bf360c !important;
+}
+
+.deep-orange-text.text-darken-4 {
+  color: #bf360c !important;
+}
+
+.deep-orange.accent-1 {
+  background-color: #ff9e80 !important;
+}
+
+.deep-orange-text.text-accent-1 {
+  color: #ff9e80 !important;
+}
+
+.deep-orange.accent-2 {
+  background-color: #ff6e40 !important;
+}
+
+.deep-orange-text.text-accent-2 {
+  color: #ff6e40 !important;
+}
+
+.deep-orange.accent-3 {
+  background-color: #ff3d00 !important;
+}
+
+.deep-orange-text.text-accent-3 {
+  color: #ff3d00 !important;
+}
+
+.deep-orange.accent-4 {
+  background-color: #dd2c00 !important;
+}
+
+.deep-orange-text.text-accent-4 {
+  color: #dd2c00 !important;
+}
+
+.brown {
+  background-color: #795548 !important;
+}
+
+.brown-text {
+  color: #795548 !important;
+}
+
+.brown.lighten-5 {
+  background-color: #efebe9 !important;
+}
+
+.brown-text.text-lighten-5 {
+  color: #efebe9 !important;
+}
+
+.brown.lighten-4 {
+  background-color: #d7ccc8 !important;
+}
+
+.brown-text.text-lighten-4 {
+  color: #d7ccc8 !important;
+}
+
+.brown.lighten-3 {
+  background-color: #bcaaa4 !important;
+}
+
+.brown-text.text-lighten-3 {
+  color: #bcaaa4 !important;
+}
+
+.brown.lighten-2 {
+  background-color: #a1887f !important;
+}
+
+.brown-text.text-lighten-2 {
+  color: #a1887f !important;
+}
+
+.brown.lighten-1 {
+  background-color: #8d6e63 !important;
+}
+
+.brown-text.text-lighten-1 {
+  color: #8d6e63 !important;
+}
+
+.brown.darken-1 {
+  background-color: #6d4c41 !important;
+}
+
+.brown-text.text-darken-1 {
+  color: #6d4c41 !important;
+}
+
+.brown.darken-2 {
+  background-color: #5d4037 !important;
+}
+
+.brown-text.text-darken-2 {
+  color: #5d4037 !important;
+}
+
+.brown.darken-3 {
+  background-color: #4e342e !important;
+}
+
+.brown-text.text-darken-3 {
+  color: #4e342e !important;
+}
+
+.brown.darken-4 {
+  background-color: #3e2723 !important;
+}
+
+.brown-text.text-darken-4 {
+  color: #3e2723 !important;
+}
+
+.blue-grey {
+  background-color: #607d8b !important;
+}
+
+.blue-grey-text {
+  color: #607d8b !important;
+}
+
+.blue-grey.lighten-5 {
+  background-color: #eceff1 !important;
+}
+
+.blue-grey-text.text-lighten-5 {
+  color: #eceff1 !important;
+}
+
+.blue-grey.lighten-4 {
+  background-color: #cfd8dc !important;
+}
+
+.blue-grey-text.text-lighten-4 {
+  color: #cfd8dc !important;
+}
+
+.blue-grey.lighten-3 {
+  background-color: #b0bec5 !important;
+}
+
+.blue-grey-text.text-lighten-3 {
+  color: #b0bec5 !important;
+}
+
+.blue-grey.lighten-2 {
+  background-color: #90a4ae !important;
+}
+
+.blue-grey-text.text-lighten-2 {
+  color: #90a4ae !important;
+}
+
+.blue-grey.lighten-1 {
+  background-color: #78909c !important;
+}
+
+.blue-grey-text.text-lighten-1 {
+  color: #78909c !important;
+}
+
+.blue-grey.darken-1 {
+  background-color: #546e7a !important;
+}
+
+.blue-grey-text.text-darken-1 {
+  color: #546e7a !important;
+}
+
+.blue-grey.darken-2 {
+  background-color: #455a64 !important;
+}
+
+.blue-grey-text.text-darken-2 {
+  color: #455a64 !important;
+}
+
+.blue-grey.darken-3 {
+  background-color: #37474f !important;
+}
+
+.blue-grey-text.text-darken-3 {
+  color: #37474f !important;
+}
+
+.blue-grey.darken-4 {
+  background-color: #263238 !important;
+}
+
+.blue-grey-text.text-darken-4 {
+  color: #263238 !important;
+}
+
+.grey {
+  background-color: #9e9e9e !important;
+}
+
+.grey-text {
+  color: #9e9e9e !important;
+}
+
+.grey.lighten-5 {
+  background-color: #fafafa !important;
+}
+
+.grey-text.text-lighten-5 {
+  color: #fafafa !important;
+}
+
+.grey.lighten-4 {
+  background-color: #f5f5f5 !important;
+}
+
+.grey-text.text-lighten-4 {
+  color: #f5f5f5 !important;
+}
+
+.grey.lighten-3 {
+  background-color: #eeeeee !important;
+}
+
+.grey-text.text-lighten-3 {
+  color: #eeeeee !important;
+}
+
+.grey.lighten-2 {
+  background-color: #e0e0e0 !important;
+}
+
+.grey-text.text-lighten-2 {
+  color: #e0e0e0 !important;
+}
+
+.grey.lighten-1 {
+  background-color: #bdbdbd !important;
+}
+
+.grey-text.text-lighten-1 {
+  color: #bdbdbd !important;
+}
+
+.grey.darken-1 {
+  background-color: #757575 !important;
+}
+
+.grey-text.text-darken-1 {
+  color: #757575 !important;
+}
+
+.grey.darken-2 {
+  background-color: #616161 !important;
+}
+
+.grey-text.text-darken-2 {
+  color: #616161 !important;
+}
+
+.grey.darken-3 {
+  background-color: #424242 !important;
+}
+
+.grey-text.text-darken-3 {
+  color: #424242 !important;
+}
+
+.grey.darken-4 {
+  background-color: #212121 !important;
+}
+
+.grey-text.text-darken-4 {
+  color: #212121 !important;
+}
+
+.black {
+  background-color: #000000 !important;
+}
+
+.black-text {
+  color: #000000 !important;
+}
+
+.white {
+  background-color: #FFFFFF !important;
+}
+
+.white-text {
+  color: #FFFFFF !important;
+}
+
+.transparent {
+  background-color: transparent !important;
+}
+
+.transparent-text {
+  color: transparent !important;
+}
+
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS and IE text size adjust after device orientation change,
+ *    without disabling user zoom.
+ */
+html {
+  font-family: sans-serif;
+  /* 1 */
+  -ms-text-size-adjust: 100%;
+  /* 2 */
+  -webkit-text-size-adjust: 100%;
+  /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+body {
+  margin: 0;
+}
+
+/* HTML5 display definitions
+   ========================================================================== */
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+audio,
+canvas,
+progress,
+video {
+  display: inline-block;
+  /* 1 */
+  vertical-align: baseline;
+  /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+ */
+[hidden],
+template {
+  display: none;
+}
+
+/* Links
+   ========================================================================== */
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+a {
+  background-color: transparent;
+}
+
+/**
+ * Improve readability of focused elements when they are also in an
+ * active/hover state.
+ */
+a:active,
+a:hover {
+  outline: 0;
+}
+
+/* Text-level semantics
+   ========================================================================== */
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+b,
+strong {
+  font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+dfn {
+  font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+mark {
+  background: #ff0;
+  color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+/* Embedded content
+   ========================================================================== */
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+img {
+  border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+svg:not(:root) {
+  overflow: hidden;
+}
+
+/* Grouping content
+   ========================================================================== */
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+figure {
+  margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+hr {
+  box-sizing: content-box;
+  height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+pre {
+  overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+/* Forms
+   ========================================================================== */
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+/**
+ * 1. Correct color not being inherited.
+ *    Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+button,
+input,
+optgroup,
+select,
+textarea {
+  color: inherit;
+  /* 1 */
+  font: inherit;
+  /* 2 */
+  margin: 0;
+  /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+button {
+  overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+button,
+select {
+  text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ */
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  /* 2 */
+  cursor: pointer;
+  /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+input {
+  line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+input[type="checkbox"],
+input[type="radio"] {
+  box-sizing: border-box;
+  /* 1 */
+  padding: 0;
+  /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+ */
+input[type="search"] {
+  -webkit-appearance: textfield;
+  /* 1 */
+  box-sizing: content-box;
+  /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+legend {
+  border: 0;
+  /* 1 */
+  padding: 0;
+  /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+textarea {
+  overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+optgroup {
+  font-weight: bold;
+}
+
+/* Tables
+   ========================================================================== */
+/**
+ * Remove most spacing between table cells.
+ */
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+td,
+th {
+  padding: 0;
+}
+
+html {
+  box-sizing: border-box;
+}
+
+*, *:before, *:after {
+  box-sizing: inherit;
+}
+
+ul:not(.browser-default) {
+  padding-left: 0;
+  list-style-type: none;
+}
+
+ul:not(.browser-default) li {
+  list-style-type: none;
+}
+
+a {
+  color: #039be5;
+  text-decoration: none;
+  -webkit-tap-highlight-color: transparent;
+}
+
+.valign-wrapper {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+}
+
+.valign-wrapper .valign {
+  display: block;
+}
+
+.clearfix {
+  clear: both;
+}
+
+.z-depth-0 {
+  box-shadow: none !important;
+}
+
+.z-depth-1, nav, .card-panel, .card, .toast, .btn, .btn-large, .btn-floating, .dropdown-content, .collapsible, .side-nav {
+  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
+}
+
+.z-depth-1-half, .btn:hover, .btn-large:hover, .btn-floating:hover {
+  box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2);
+}
+
+.z-depth-2 {
+  box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
+}
+
+.z-depth-3 {
+  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3);
+}
+
+.z-depth-4, .modal {
+  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.3);
+}
+
+.z-depth-5 {
+  box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.3);
+}
+
+.hoverable {
+  transition: box-shadow .25s;
+  box-shadow: 0;
+}
+
+.hoverable:hover {
+  transition: box-shadow .25s;
+  box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
+}
+
+.divider {
+  height: 1px;
+  overflow: hidden;
+  background-color: #e0e0e0;
+}
+
+blockquote {
+  margin: 20px 0;
+  padding-left: 1.5rem;
+  border-left: 5px solid #ee6e73;
+}
+
+i {
+  line-height: inherit;
+}
+
+i.left {
+  float: left;
+  margin-right: 15px;
+}
+
+i.right {
+  float: right;
+  margin-left: 15px;
+}
+
+i.tiny {
+  font-size: 1rem;
+}
+
+i.small {
+  font-size: 2rem;
+}
+
+i.medium {
+  font-size: 4rem;
+}
+
+i.large {
+  font-size: 6rem;
+}
+
+img.responsive-img,
+video.responsive-video {
+  max-width: 100%;
+  height: auto;
+}
+
+.pagination li {
+  display: inline-block;
+  border-radius: 2px;
+  text-align: center;
+  vertical-align: top;
+  height: 30px;
+}
+
+.pagination li a {
+  color: #444;
+  display: inline-block;
+  font-size: 1.2rem;
+  padding: 0 10px;
+  line-height: 30px;
+}
+
+.pagination li.active a {
+  color: #fff;
+}
+
+.pagination li.active {
+  background-color: #ee6e73;
+}
+
+.pagination li.disabled a {
+  cursor: default;
+  color: #999;
+}
+
+.pagination li i {
+  font-size: 2rem;
+}
+
+.pagination li.pages ul li {
+  display: inline-block;
+  float: none;
+}
+
+@media only screen and (max-width: 992px) {
+  .pagination {
+    width: 100%;
+  }
+  .pagination li.prev,
+  .pagination li.next {
+    width: 10%;
+  }
+  .pagination li.pages {
+    width: 80%;
+    overflow: hidden;
+    white-space: nowrap;
+  }
+}
+
+.breadcrumb {
+  font-size: 18px;
+  color: rgba(255, 255, 255, 0.7);
+}
+
+.breadcrumb i,
+.breadcrumb [class^="mdi-"], .breadcrumb [class*="mdi-"],
+.breadcrumb i.material-icons {
+  display: inline-block;
+  float: left;
+  font-size: 24px;
+}
+
+.breadcrumb:before {
+  content: '\E5CC';
+  color: rgba(255, 255, 255, 0.7);
+  vertical-align: top;
+  display: inline-block;
+  font-family: 'Material Icons';
+  font-weight: normal;
+  font-style: normal;
+  font-size: 25px;
+  margin: 0 10px 0 8px;
+  -webkit-font-smoothing: antialiased;
+}
+
+.breadcrumb:first-child:before {
+  display: none;
+}
+
+.breadcrumb:last-child {
+  color: #fff;
+}
+
+.parallax-container {
+  position: relative;
+  overflow: hidden;
+  height: 500px;
+}
+
+.parallax {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: -1;
+}
+
+.parallax img {
+  display: none;
+  position: absolute;
+  left: 50%;
+  bottom: 0;
+  min-width: 100%;
+  min-height: 100%;
+  -webkit-transform: translate3d(0, 0, 0);
+  transform: translate3d(0, 0, 0);
+  -webkit-transform: translateX(-50%);
+          transform: translateX(-50%);
+}
+
+.pin-top, .pin-bottom {
+  position: relative;
+}
+
+.pinned {
+  position: fixed !important;
+}
+
+/*********************
+  Transition Classes
+**********************/
+ul.staggered-list li {
+  opacity: 0;
+}
+
+.fade-in {
+  opacity: 0;
+  -webkit-transform-origin: 0 50%;
+          transform-origin: 0 50%;
+}
+
+/*********************
+  Media Query Classes
+**********************/
+@media only screen and (max-width: 600px) {
+  .hide-on-small-only, .hide-on-small-and-down {
+    display: none !important;
+  }
+}
+
+@media only screen and (max-width: 992px) {
+  .hide-on-med-and-down {
+    display: none !important;
+  }
+}
+
+@media only screen and (min-width: 601px) {
+  .hide-on-med-and-up {
+    display: none !important;
+  }
+}
+
+@media only screen and (min-width: 600px) and (max-width: 992px) {
+  .hide-on-med-only {
+    display: none !important;
+  }
+}
+
+@media only screen and (min-width: 993px) {
+  .hide-on-large-only {
+    display: none !important;
+  }
+}
+
+@media only screen and (min-width: 993px) {
+  .show-on-large {
+    display: block !important;
+  }
+}
+
+@media only screen and (min-width: 600px) and (max-width: 992px) {
+  .show-on-medium {
+    display: block !important;
+  }
+}
+
+@media only screen and (max-width: 600px) {
+  .show-on-small {
+    display: block !important;
+  }
+}
+
+@media only screen and (min-width: 601px) {
+  .show-on-medium-and-up {
+    display: block !important;
+  }
+}
+
+@media only screen and (max-width: 992px) {
+  .show-on-medium-and-down {
+    display: block !important;
+  }
+}
+
+@media only screen and (max-width: 600px) {
+  .center-on-small-only {
+    text-align: center;
+  }
+}
+
+footer.page-footer {
+  padding-top: 20px;
+  background-color: #ee6e73;
+}
+
+footer.page-footer .footer-copyright {
+  overflow: hidden;
+  min-height: 50px;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+  padding: 10px 0px;
+  color: rgba(255, 255, 255, 0.8);
+  background-color: rgba(51, 51, 51, 0.08);
+}
+
+table, th, td {
+  border: none;
+}
+
+table {
+  width: 100%;
+  display: table;
+}
+
+table.bordered > thead > tr,
+table.bordered > tbody > tr {
+  border-bottom: 1px solid #d0d0d0;
+}
+
+table.striped > tbody > tr:nth-child(odd) {
+  background-color: #f2f2f2;
+}
+
+table.striped > tbody > tr > td {
+  border-radius: 0;
+}
+
+table.highlight > tbody > tr {
+  transition: background-color .25s ease;
+}
+
+table.highlight > tbody > tr:hover {
+  background-color: #f2f2f2;
+}
+
+table.centered thead tr th, table.centered tbody tr td {
+  text-align: center;
+}
+
+thead {
+  border-bottom: 1px solid #d0d0d0;
+}
+
+td, th {
+  padding: 15px 5px;
+  display: table-cell;
+  text-align: left;
+  vertical-align: middle;
+  border-radius: 2px;
+}
+
+@media only screen and (max-width: 992px) {
+  table.responsive-table {
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+    display: block;
+    position: relative;
+    /* sort out borders */
+  }
+  table.responsive-table td:empty:before {
+    content: '\00a0';
+  }
+  table.responsive-table th,
+  table.responsive-table td {
+    margin: 0;
+    vertical-align: top;
+  }
+  table.responsive-table th {
+    text-align: left;
+  }
+  table.responsive-table thead {
+    display: block;
+    float: left;
+  }
+  table.responsive-table thead tr {
+    display: block;
+    padding: 0 10px 0 0;
+  }
+  table.responsive-table thead tr th::before {
+    content: "\00a0";
+  }
+  table.responsive-table tbody {
+    display: block;
+    width: auto;
+    position: relative;
+    overflow-x: auto;
+    white-space: nowrap;
+  }
+  table.responsive-table tbody tr {
+    display: inline-block;
+    vertical-align: top;
+  }
+  table.responsive-table th {
+    display: block;
+    text-align: right;
+  }
+  table.responsive-table td {
+    display: block;
+    min-height: 1.25em;
+    text-align: left;
+  }
+  table.responsive-table tr {
+    padding: 0 10px;
+  }
+  table.responsive-table thead {
+    border: 0;
+    border-right: 1px solid #d0d0d0;
+  }
+  table.responsive-table.bordered th {
+    border-bottom: 0;
+    border-left: 0;
+  }
+  table.responsive-table.bordered td {
+    border-left: 0;
+    border-right: 0;
+    border-bottom: 0;
+  }
+  table.responsive-table.bordered tr {
+    border: 0;
+  }
+  table.responsive-table.bordered tbody tr {
+    border-right: 1px solid #d0d0d0;
+  }
+}
+
+.collection {
+  margin: 0.5rem 0 1rem 0;
+  border: 1px solid #e0e0e0;
+  border-radius: 2px;
+  overflow: hidden;
+  position: relative;
+}
+
+.collection .collection-item {
+  background-color: #fff;
+  line-height: 1.5rem;
+  padding: 10px 20px;
+  margin: 0;
+  border-bottom: 1px solid #e0e0e0;
+}
+
+.collection .collection-item.avatar {
+  min-height: 84px;
+  padding-left: 72px;
+  position: relative;
+}
+
+.collection .collection-item.avatar .circle {
+  position: absolute;
+  width: 42px;
+  height: 42px;
+  overflow: hidden;
+  left: 15px;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.collection .collection-item.avatar i.circle {
+  font-size: 18px;
+  line-height: 42px;
+  color: #fff;
+  background-color: #999;
+  text-align: center;
+}
+
+.collection .collection-item.avatar .title {
+  font-size: 16px;
+}
+
+.collection .collection-item.avatar p {
+  margin: 0;
+}
+
+.collection .collection-item.avatar .secondary-content {
+  position: absolute;
+  top: 16px;
+  right: 16px;
+}
+
+.collection .collection-item:last-child {
+  border-bottom: none;
+}
+
+.collection .collection-item.active {
+  background-color: #26a69a;
+  color: #eafaf9;
+}
+
+.collection .collection-item.active .secondary-content {
+  color: #fff;
+}
+
+.collection a.collection-item {
+  display: block;
+  transition: .25s;
+  color: #26a69a;
+}
+
+.collection a.collection-item:not(.active):hover {
+  background-color: #ddd;
+}
+
+.collection.with-header .collection-header {
+  background-color: #fff;
+  border-bottom: 1px solid #e0e0e0;
+  padding: 10px 20px;
+}
+
+.collection.with-header .collection-item {
+  padding-left: 30px;
+}
+
+.collection.with-header .collection-item.avatar {
+  padding-left: 72px;
+}
+
+.secondary-content {
+  float: right;
+  color: #26a69a;
+}
+
+.collapsible .collection {
+  margin: 0;
+  border: none;
+}
+
+.video-container {
+  position: relative;
+  padding-bottom: 56.25%;
+  height: 0;
+  overflow: hidden;
+}
+
+.video-container iframe, .video-container object, .video-container embed {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.progress {
+  position: relative;
+  height: 4px;
+  display: block;
+  width: 100%;
+  background-color: #acece6;
+  border-radius: 2px;
+  margin: 0.5rem 0 1rem 0;
+  overflow: hidden;
+}
+
+.progress .determinate {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  background-color: #26a69a;
+  transition: width .3s linear;
+}
+
+.progress .indeterminate {
+  background-color: #26a69a;
+}
+
+.progress .indeterminate:before {
+  content: '';
+  position: absolute;
+  background-color: inherit;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  will-change: left, right;
+  -webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
+          animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
+}
+
+.progress .indeterminate:after {
+  content: '';
+  position: absolute;
+  background-color: inherit;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  will-change: left, right;
+  -webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
+          animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
+  -webkit-animation-delay: 1.15s;
+          animation-delay: 1.15s;
+}
+
+@-webkit-keyframes indeterminate {
+  0% {
+    left: -35%;
+    right: 100%;
+  }
+  60% {
+    left: 100%;
+    right: -90%;
+  }
+  100% {
+    left: 100%;
+    right: -90%;
+  }
+}
+
+@keyframes indeterminate {
+  0% {
+    left: -35%;
+    right: 100%;
+  }
+  60% {
+    left: 100%;
+    right: -90%;
+  }
+  100% {
+    left: 100%;
+    right: -90%;
+  }
+}
+
+@-webkit-keyframes indeterminate-short {
+  0% {
+    left: -200%;
+    right: 100%;
+  }
+  60% {
+    left: 107%;
+    right: -8%;
+  }
+  100% {
+    left: 107%;
+    right: -8%;
+  }
+}
+
+@keyframes indeterminate-short {
+  0% {
+    left: -200%;
+    right: 100%;
+  }
+  60% {
+    left: 107%;
+    right: -8%;
+  }
+  100% {
+    left: 107%;
+    right: -8%;
+  }
+}
+
+/*******************
+  Utility Classes
+*******************/
+.hide {
+  display: none !important;
+}
+
+.left-align {
+  text-align: left;
+}
+
+.right-align {
+  text-align: right;
+}
+
+.center, .center-align {
+  text-align: center;
+}
+
+.left {
+  float: left !important;
+}
+
+.right {
+  float: right !important;
+}
+
+.no-select, input[type=range],
+input[type=range] + .thumb {
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+.circle {
+  border-radius: 50%;
+}
+
+.center-block {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.truncate {
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.no-padding {
+  padding: 0 !important;
+}
+
+span.badge {
+  min-width: 3rem;
+  padding: 0 6px;
+  margin-left: 14px;
+  text-align: center;
+  font-size: 1rem;
+  line-height: 22px;
+  height: 22px;
+  color: #757575;
+  float: right;
+  box-sizing: border-box;
+}
+
+span.badge.new {
+  font-weight: 300;
+  font-size: 0.8rem;
+  color: #fff;
+  background-color: #26a69a;
+  border-radius: 2px;
+}
+
+span.badge.new:after {
+  content: " new";
+}
+
+span.badge[data-badge-caption]::after {
+  content: " " attr(data-badge-caption);
+}
+
+nav ul a span.badge {
+  display: inline-block;
+  float: none;
+  margin-left: 4px;
+  line-height: 22px;
+  height: 22px;
+}
+
+.collection-item span.badge {
+  margin-top: calc(0.75rem - 11px);
+}
+
+.collapsible span.badge {
+  margin-top: calc(1.5rem - 11px);
+}
+
+.side-nav span.badge {
+  margin-top: calc(24px - 11px);
+}
+
+/* This is needed for some mobile phones to display the Google Icon font properly */
+.material-icons {
+  text-rendering: optimizeLegibility;
+  -webkit-font-feature-settings: 'liga';
+     -moz-font-feature-settings: 'liga';
+          font-feature-settings: 'liga';
+}
+
+.container {
+  margin: 0 auto;
+  max-width: 1280px;
+  width: 90%;
+}
+
+@media only screen and (min-width: 601px) {
+  .container {
+    width: 85%;
+  }
+}
+
+@media only screen and (min-width: 993px) {
+  .container {
+    width: 70%;
+  }
+}
+
+.container .row {
+  margin-left: -0.75rem;
+  margin-right: -0.75rem;
+}
+
+.section {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+}
+
+.section.no-pad {
+  padding: 0;
+}
+
+.section.no-pad-bot {
+  padding-bottom: 0;
+}
+
+.section.no-pad-top {
+  padding-top: 0;
+}
+
+.row {
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 20px;
+}
+
+.row:after {
+  content: "";
+  display: table;
+  clear: both;
+}
+
+.row .col {
+  float: left;
+  box-sizing: border-box;
+  padding: 0 0.75rem;
+  min-height: 1px;
+}
+
+.row .col[class*="push-"], .row .col[class*="pull-"] {
+  position: relative;
+}
+
+.row .col.s1 {
+  width: 8.3333333333%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s2 {
+  width: 16.6666666667%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s3 {
+  width: 25%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s4 {
+  width: 33.3333333333%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s5 {
+  width: 41.6666666667%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s6 {
+  width: 50%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s7 {
+  width: 58.3333333333%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s8 {
+  width: 66.6666666667%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s9 {
+  width: 75%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s10 {
+  width: 83.3333333333%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s11 {
+  width: 91.6666666667%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.s12 {
+  width: 100%;
+  margin-left: auto;
+  left: auto;
+  right: auto;
+}
+
+.row .col.offset-s1 {
+  margin-left: 8.3333333333%;
+}
+
+.row .col.pull-s1 {
+  right: 8.3333333333%;
+}
+
+.row .col.push-s1 {
+  left: 8.3333333333%;
+}
+
+.row .col.offset-s2 {
+  margin-left: 16.6666666667%;
+}
+
+.row .col.pull-s2 {
+  right: 16.6666666667%;
+}
+
+.row .col.push-s2 {
+  left: 16.6666666667%;
+}
+
+.row .col.offset-s3 {
+  margin-left: 25%;
+}
+
+.row .col.pull-s3 {
+  right: 25%;
+}
+
+.row .col.push-s3 {
+  left: 25%;
+}
+
+.row .col.offset-s4 {
+  margin-left: 33.3333333333%;
+}
+
+.row .col.pull-s4 {
+  right: 33.3333333333%;
+}
+
+.row .col.push-s4 {
+  left: 33.3333333333%;
+}
+
+.row .col.offset-s5 {
+  margin-left: 41.6666666667%;
+}
+
+.row .col.pull-s5 {
+  right: 41.6666666667%;
+}
+
+.row .col.push-s5 {
+  left: 41.6666666667%;
+}
+
+.row .col.offset-s6 {
+  margin-left: 50%;
+}
+
+.row .col.pull-s6 {
+  right: 50%;
+}
+
+.row .col.push-s6 {
+  left: 50%;
+}
+
+.row .col.offset-s7 {
+  margin-left: 58.3333333333%;
+}
+
+.row .col.pull-s7 {
+  right: 58.3333333333%;
+}
+
+.row .col.push-s7 {
+  left: 58.3333333333%;
+}
+
+.row .col.offset-s8 {
+  margin-left: 66.6666666667%;
+}
+
+.row .col.pull-s8 {
+  right: 66.6666666667%;
+}
+
+.row .col.push-s8 {
+  left: 66.6666666667%;
+}
+
+.row .col.offset-s9 {
+  margin-left: 75%;
+}
+
+.row .col.pull-s9 {
+  right: 75%;
+}
+
+.row .col.push-s9 {
+  left: 75%;
+}
+
+.row .col.offset-s10 {
+  margin-left: 83.3333333333%;
+}
+
+.row .col.pull-s10 {
+  right: 83.3333333333%;
+}
+
+.row .col.push-s10 {
+  left: 83.3333333333%;
+}
+
+.row .col.offset-s11 {
+  margin-left: 91.6666666667%;
+}
+
+.row .col.pull-s11 {
+  right: 91.6666666667%;
+}
+
+.row .col.push-s11 {
+  left: 91.6666666667%;
+}
+
+.row .col.offset-s12 {
+  margin-left: 100%;
+}
+
+.row .col.pull-s12 {
+  right: 100%;
+}
+
+.row .col.push-s12 {
+  left: 100%;
+}
+
+@media only screen and (min-width: 601px) {
+  .row .col.m1 {
+    width: 8.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m2 {
+    width: 16.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m3 {
+    width: 25%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m4 {
+    width: 33.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m5 {
+    width: 41.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m6 {
+    width: 50%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m7 {
+    width: 58.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m8 {
+    width: 66.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m9 {
+    width: 75%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m10 {
+    width: 83.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m11 {
+    width: 91.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.m12 {
+    width: 100%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.offset-m1 {
+    margin-left: 8.3333333333%;
+  }
+  .row .col.pull-m1 {
+    right: 8.3333333333%;
+  }
+  .row .col.push-m1 {
+    left: 8.3333333333%;
+  }
+  .row .col.offset-m2 {
+    margin-left: 16.6666666667%;
+  }
+  .row .col.pull-m2 {
+    right: 16.6666666667%;
+  }
+  .row .col.push-m2 {
+    left: 16.6666666667%;
+  }
+  .row .col.offset-m3 {
+    margin-left: 25%;
+  }
+  .row .col.pull-m3 {
+    right: 25%;
+  }
+  .row .col.push-m3 {
+    left: 25%;
+  }
+  .row .col.offset-m4 {
+    margin-left: 33.3333333333%;
+  }
+  .row .col.pull-m4 {
+    right: 33.3333333333%;
+  }
+  .row .col.push-m4 {
+    left: 33.3333333333%;
+  }
+  .row .col.offset-m5 {
+    margin-left: 41.6666666667%;
+  }
+  .row .col.pull-m5 {
+    right: 41.6666666667%;
+  }
+  .row .col.push-m5 {
+    left: 41.6666666667%;
+  }
+  .row .col.offset-m6 {
+    margin-left: 50%;
+  }
+  .row .col.pull-m6 {
+    right: 50%;
+  }
+  .row .col.push-m6 {
+    left: 50%;
+  }
+  .row .col.offset-m7 {
+    margin-left: 58.3333333333%;
+  }
+  .row .col.pull-m7 {
+    right: 58.3333333333%;
+  }
+  .row .col.push-m7 {
+    left: 58.3333333333%;
+  }
+  .row .col.offset-m8 {
+    margin-left: 66.6666666667%;
+  }
+  .row .col.pull-m8 {
+    right: 66.6666666667%;
+  }
+  .row .col.push-m8 {
+    left: 66.6666666667%;
+  }
+  .row .col.offset-m9 {
+    margin-left: 75%;
+  }
+  .row .col.pull-m9 {
+    right: 75%;
+  }
+  .row .col.push-m9 {
+    left: 75%;
+  }
+  .row .col.offset-m10 {
+    margin-left: 83.3333333333%;
+  }
+  .row .col.pull-m10 {
+    right: 83.3333333333%;
+  }
+  .row .col.push-m10 {
+    left: 83.3333333333%;
+  }
+  .row .col.offset-m11 {
+    margin-left: 91.6666666667%;
+  }
+  .row .col.pull-m11 {
+    right: 91.6666666667%;
+  }
+  .row .col.push-m11 {
+    left: 91.6666666667%;
+  }
+  .row .col.offset-m12 {
+    margin-left: 100%;
+  }
+  .row .col.pull-m12 {
+    right: 100%;
+  }
+  .row .col.push-m12 {
+    left: 100%;
+  }
+}
+
+@media only screen and (min-width: 993px) {
+  .row .col.l1 {
+    width: 8.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l2 {
+    width: 16.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l3 {
+    width: 25%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l4 {
+    width: 33.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l5 {
+    width: 41.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l6 {
+    width: 50%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l7 {
+    width: 58.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l8 {
+    width: 66.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l9 {
+    width: 75%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l10 {
+    width: 83.3333333333%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l11 {
+    width: 91.6666666667%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.l12 {
+    width: 100%;
+    margin-left: auto;
+    left: auto;
+    right: auto;
+  }
+  .row .col.offset-l1 {
+    margin-left: 8.3333333333%;
+  }
+  .row .col.pull-l1 {
+    right: 8.3333333333%;
+  }
+  .row .col.push-l1 {
+    left: 8.3333333333%;
+  }
+  .row .col.offset-l2 {
+    margin-left: 16.6666666667%;
+  }
+  .row .col.pull-l2 {
+    right: 16.6666666667%;
+  }
+  .row .col.push-l2 {
+    left: 16.6666666667%;
+  }
+  .row .col.offset-l3 {
+    margin-left: 25%;
+  }
+  .row .col.pull-l3 {
+    right: 25%;
+  }
+  .row .col.push-l3 {
+    left: 25%;
+  }
+  .row .col.offset-l4 {
+    margin-left: 33.3333333333%;
+  }
+  .row .col.pull-l4 {
+    right: 33.3333333333%;
+  }
+  .row .col.push-l4 {
+    left: 33.3333333333%;
+  }
+  .row .col.offset-l5 {
+    margin-left: 41.6666666667%;
+  }
+  .row .col.pull-l5 {
+    right: 41.6666666667%;
+  }
+  .row .col.push-l5 {
+    left: 41.6666666667%;
+  }
+  .row .col.offset-l6 {
+    margin-left: 50%;
+  }
+  .row .col.pull-l6 {
+    right: 50%;
+  }
+  .row .col.push-l6 {
+    left: 50%;
+  }
+  .row .col.offset-l7 {
+    margin-left: 58.3333333333%;
+  }
+  .row .col.pull-l7 {
+    right: 58.3333333333%;
+  }
+  .row .col.push-l7 {
+    left: 58.3333333333%;
+  }
+  .row .col.offset-l8 {
+    margin-left: 66.6666666667%;
+  }
+  .row .col.pull-l8 {
+    right: 66.6666666667%;
+  }
+  .row .col.push-l8 {
+    left: 66.6666666667%;
+  }
+  .row .col.offset-l9 {
+    margin-left: 75%;
+  }
+  .row .col.pull-l9 {
+    right: 75%;
+  }
+  .row .col.push-l9 {
+    left: 75%;
+  }
+  .row .col.offset-l10 {
+    margin-left: 83.3333333333%;
+  }
+  .row .col.pull-l10 {
+    right: 83.3333333333%;
+  }
+  .row .col.push-l10 {
+    left: 83.3333333333%;
+  }
+  .row .col.offset-l11 {
+    margin-left: 91.6666666667%;
+  }
+  .row .col.pull-l11 {
+    right: 91.6666666667%;
+  }
+  .row .col.push-l11 {
+    left: 91.6666666667%;
+  }
+  .row .col.offset-l12 {
+    margin-left: 100%;
+  }
+  .row .col.pull-l12 {
+    right: 100%;
+  }
+  .row .col.push-l12 {
+    left: 100%;
+  }
+}
+
+nav {
+  color: #fff;
+  background-color: #ee6e73;
+  width: 100%;
+  height: 56px;
+  line-height: 56px;
+}
+
+nav.nav-extended {
+  height: auto;
+}
+
+nav.nav-extended .nav-wrapper {
+  min-height: 56px;
+  height: auto;
+}
+
+nav.nav-extended .nav-content {
+  position: relative;
+  line-height: normal;
+}
+
+nav a {
+  color: #fff;
+}
+
+nav i,
+nav [class^="mdi-"], nav [class*="mdi-"],
+nav i.material-icons {
+  display: block;
+  font-size: 24px;
+  height: 56px;
+  line-height: 56px;
+}
+
+nav .nav-wrapper {
+  position: relative;
+  height: 100%;
+}
+
+@media only screen and (min-width: 993px) {
+  nav a.button-collapse {
+    display: none;
+  }
+}
+
+nav .button-collapse {
+  float: left;
+  position: relative;
+  z-index: 1;
+  height: 56px;
+  margin: 0 18px;
+}
+
+nav .button-collapse i {
+  height: 56px;
+  line-height: 56px;
+}
+
+nav .brand-logo {
+  position: absolute;
+  color: #fff;
+  display: inline-block;
+  font-size: 2.1rem;
+  padding: 0;
+  white-space: nowrap;
+}
+
+nav .brand-logo.center {
+  left: 50%;
+  -webkit-transform: translateX(-50%);
+          transform: translateX(-50%);
+}
+
+@media only screen and (max-width: 992px) {
+  nav .brand-logo {
+    left: 50%;
+    -webkit-transform: translateX(-50%);
+            transform: translateX(-50%);
+  }
+  nav .brand-logo.left, nav .brand-logo.right {
+    padding: 0;
+    -webkit-transform: none;
+            transform: none;
+  }
+  nav .brand-logo.left {
+    left: 0.5rem;
+  }
+  nav .brand-logo.right {
+    right: 0.5rem;
+    left: auto;
+  }
+}
+
+nav .brand-logo.right {
+  right: 0.5rem;
+  padding: 0;
+}
+
+nav .brand-logo i,
+nav .brand-logo [class^="mdi-"], nav .brand-logo [class*="mdi-"],
+nav .brand-logo i.material-icons {
+  float: left;
+  margin-right: 15px;
+}
+
+nav .nav-title {
+  display: inline-block;
+  font-size: 32px;
+  padding: 28px 0;
+}
+
+nav ul {
+  margin: 0;
+}
+
+nav ul li {
+  transition: background-color .3s;
+  float: left;
+  padding: 0;
+}
+
+nav ul li.active {
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+nav ul a {
+  transition: background-color .3s;
+  font-size: 1rem;
+  color: #fff;
+  display: block;
+  padding: 0 15px;
+  cursor: pointer;
+}
+
+nav ul a.btn, nav ul a.btn-large, nav ul a.btn-large, nav ul a.btn-flat, nav ul a.btn-floating {
+  margin-top: -2px;
+  margin-left: 15px;
+  margin-right: 15px;
+}
+
+nav ul a.btn > .material-icons, nav ul a.btn-large > .material-icons, nav ul a.btn-large > .material-icons, nav ul a.btn-flat > .material-icons, nav ul a.btn-floating > .material-icons {
+  height: inherit;
+  line-height: inherit;
+}
+
+nav ul a:hover {
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+nav ul.left {
+  float: left;
+}
+
+nav form {
+  height: 100%;
+}
+
+nav .input-field {
+  margin: 0;
+  height: 100%;
+}
+
+nav .input-field input {
+  height: 100%;
+  font-size: 1.2rem;
+  border: none;
+  padding-left: 2rem;
+}
+
+nav .input-field input:focus, nav .input-field input[type=text]:valid, nav .input-field input[type=password]:valid, nav .input-field input[type=email]:valid, nav .input-field input[type=url]:valid, nav .input-field input[type=date]:valid {
+  border: none;
+  box-shadow: none;
+}
+
+nav .input-field label {
+  top: 0;
+  left: 0;
+}
+
+nav .input-field label i {
+  color: rgba(255, 255, 255, 0.7);
+  transition: color .3s;
+}
+
+nav .input-field label.active i {
+  color: #fff;
+}
+
+.navbar-fixed {
+  position: relative;
+  height: 56px;
+  z-index: 997;
+}
+
+.navbar-fixed nav {
+  position: fixed;
+}
+
+@media only screen and (min-width: 601px) {
+  nav.nav-extended .nav-wrapper {
+    min-height: 64px;
+  }
+  nav, nav .nav-wrapper i, nav a.button-collapse, nav a.button-collapse i {
+    height: 64px;
+    line-height: 64px;
+  }
+  .navbar-fixed {
+    height: 64px;
+  }
+}
+
+@font-face {
+  font-family: "Roboto";
+  src: local(Roboto Thin), url("../fonts/roboto/Roboto-Thin.eot");
+  src: url("../fonts/roboto/Roboto-Thin.eot?#iefix") format("embedded-opentype"), url("../fonts/roboto/Roboto-Thin.woff2") format("woff2"), url("../fonts/roboto/Roboto-Thin.woff") format("woff"), url("../fonts/roboto/Roboto-Thin.ttf") format("truetype");
+  font-weight: 200;
+}
+
+@font-face {
+  font-family: "Roboto";
+  src: local(Roboto Light), url("../fonts/roboto/Roboto-Light.eot");
+  src: url("../fonts/roboto/Roboto-Light.eot?#iefix") format("embedded-opentype"), url("../fonts/roboto/Roboto-Light.woff2") format("woff2"), url("../fonts/roboto/Roboto-Light.woff") format("woff"), url("../fonts/roboto/Roboto-Light.ttf") format("truetype");
+  font-weight: 300;
+}
+
+@font-face {
+  font-family: "Roboto";
+  src: local(Roboto Regular), url("../fonts/roboto/Roboto-Regular.eot");
+  src: url("../fonts/roboto/Roboto-Regular.eot?#iefix") format("embedded-opentype"), url("../fonts/roboto/Roboto-Regular.woff2") format("woff2"), url("../fonts/roboto/Roboto-Regular.woff") format("woff"), url("../fonts/roboto/Roboto-Regular.ttf") format("truetype");
+  font-weight: 400;
+}
+
+@font-face {
+  font-family: "Roboto";
+  src: url("../fonts/roboto/Roboto-Medium.eot");
+  src: url("../fonts/roboto/Roboto-Medium.eot?#iefix") format("embedded-opentype"), url("../fonts/roboto/Roboto-Medium.woff2") format("woff2"), url("../fonts/roboto/Roboto-Medium.woff") format("woff"), url("../fonts/roboto/Roboto-Medium.ttf") format("truetype");
+  font-weight: 500;
+}
+
+@font-face {
+  font-family: "Roboto";
+  src: url("../fonts/roboto/Roboto-Bold.eot");
+  src: url("../fonts/roboto/Roboto-Bold.eot?#iefix") format("embedded-opentype"), url("../fonts/roboto/Roboto-Bold.woff2") format("woff2"), url("../fonts/roboto/Roboto-Bold.woff") format("woff"), url("../fonts/roboto/Roboto-Bold.ttf") format("truetype");
+  font-weight: 700;
+}
+
+a {
+  text-decoration: none;
+}
+
+html {
+  line-height: 1.5;
+  font-family: "Roboto", sans-serif;
+  font-weight: normal;
+  color: rgba(0, 0, 0, 0.87);
+}
+
+@media only screen and (min-width: 0) {
+  html {
+    font-size: 14px;
+  }
+}
+
+@media only screen and (min-width: 992px) {
+  html {
+    font-size: 14.5px;
+  }
+}
+
+@media only screen and (min-width: 1200px) {
+  html {
+    font-size: 15px;
+  }
+}
+
+h1, h2, h3, h4, h5, h6 {
+  font-weight: 400;
+  line-height: 1.1;
+}
+
+h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
+  font-weight: inherit;
+}
+
+h1 {
+  font-size: 4.2rem;
+  line-height: 110%;
+  margin: 2.1rem 0 1.68rem 0;
+}
+
+h2 {
+  font-size: 3.56rem;
+  line-height: 110%;
+  margin: 1.78rem 0 1.424rem 0;
+}
+
+h3 {
+  font-size: 2.92rem;
+  line-height: 110%;
+  margin: 1.46rem 0 1.168rem 0;
+}
+
+h4 {
+  font-size: 2.28rem;
+  line-height: 110%;
+  margin: 1.14rem 0 0.912rem 0;
+}
+
+h5 {
+  font-size: 1.64rem;
+  line-height: 110%;
+  margin: 0.82rem 0 0.656rem 0;
+}
+
+h6 {
+  font-size: 1rem;
+  line-height: 110%;
+  margin: 0.5rem 0 0.4rem 0;
+}
+
+em {
+  font-style: italic;
+}
+
+strong {
+  font-weight: 500;
+}
+
+small {
+  font-size: 75%;
+}
+
+.light, footer.page-footer .footer-copyright {
+  font-weight: 300;
+}
+
+.thin {
+  font-weight: 200;
+}
+
+.flow-text {
+  font-weight: 300;
+}
+
+@media only screen and (min-width: 360px) {
+  .flow-text {
+    font-size: 1.2rem;
+  }
+}
+
+@media only screen and (min-width: 390px) {
+  .flow-text {
+    font-size: 1.224rem;
+  }
+}
+
+@media only screen and (min-width: 420px) {
+  .flow-text {
+    font-size: 1.248rem;
+  }
+}
+
+@media only screen and (min-width: 450px) {
+  .flow-text {
+    font-size: 1.272rem;
+  }
+}
+
+@media only screen and (min-width: 480px) {
+  .flow-text {
+    font-size: 1.296rem;
+  }
+}
+
+@media only screen and (min-width: 510px) {
+  .flow-text {
+    font-size: 1.32rem;
+  }
+}
+
+@media only screen and (min-width: 540px) {
+  .flow-text {
+    font-size: 1.344rem;
+  }
+}
+
+@media only screen and (min-width: 570px) {
+  .flow-text {
+    font-size: 1.368rem;
+  }
+}
+
+@media only screen and (min-width: 600px) {
+  .flow-text {
+    font-size: 1.392rem;
+  }
+}
+
+@media only screen and (min-width: 630px) {
+  .flow-text {
+    font-size: 1.416rem;
+  }
+}
+
+@media only screen and (min-width: 660px) {
+  .flow-text {
+    font-size: 1.44rem;
+  }
+}
+
+@media only screen and (min-width: 690px) {
+  .flow-text {
+    font-size: 1.464rem;
+  }
+}
+
+@media only screen and (min-width: 720px) {
+  .flow-text {
+    font-size: 1.488rem;
+  }
+}
+
+@media only screen and (min-width: 750px) {
+  .flow-text {
+    font-size: 1.512rem;
+  }
+}
+
+@media only screen and (min-width: 780px) {
+  .flow-text {
+    font-size: 1.536rem;
+  }
+}
+
+@media only screen and (min-width: 810px) {
+  .flow-text {
+    font-size: 1.56rem;
+  }
+}
+
+@media only screen and (min-width: 840px) {
+  .flow-text {
+    font-size: 1.584rem;
+  }
+}
+
+@media only screen and (min-width: 870px) {
+  .flow-text {
+    font-size: 1.608rem;
+  }
+}
+
+@media only screen and (min-width: 900px) {
+  .flow-text {
+    font-size: 1.632rem;
+  }
+}
+
+@media only screen and (min-width: 930px) {
+  .flow-text {
+    font-size: 1.656rem;
+  }
+}
+
+@media only screen and (min-width: 960px) {
+  .flow-text {
+    font-size: 1.68rem;
+  }
+}
+
+@media only screen and (max-width: 360px) {
+  .flow-text {
+    font-size: 1.2rem;
+  }
+}
+
+.scale-transition {
+  transition: -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;
+  transition: transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;
+  transition: transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;
+}
+
+.scale-transition.scale-out {
+  -webkit-transform: scale(0);
+          transform: scale(0);
+  transition: -webkit-transform .2s !important;
+  transition: transform .2s !important;
+  transition: transform .2s, -webkit-transform .2s !important;
+}
+
+.scale-transition.scale-in {
+  -webkit-transform: scale(1);
+          transform: scale(1);
+}
+
+.card-panel {
+  transition: box-shadow .25s;
+  padding: 24px;
+  margin: 0.5rem 0 1rem 0;
+  border-radius: 2px;
+  background-color: #fff;
+}
+
+.card {
+  position: relative;
+  margin: 0.5rem 0 1rem 0;
+  background-color: #fff;
+  transition: box-shadow .25s;
+  border-radius: 2px;
+}
+
+.card .card-title {
+  font-size: 24px;
+  font-weight: 300;
+}
+
+.card .card-title.activator {
+  cursor: pointer;
+}
+
+.card.small, .card.medium, .card.large {
+  position: relative;
+}
+
+.card.small .card-image, .card.medium .card-image, .card.large .card-image {
+  max-height: 60%;
+  overflow: hidden;
+}
+
+.card.small .card-image + .card-content, .card.medium .card-image + .card-content, .card.large .card-image + .card-content {
+  max-height: 40%;
+}
+
+.card.small .card-content, .card.medium .card-content, .card.large .card-content {
+  max-height: 100%;
+  overflow: hidden;
+}
+
+.card.small .card-action, .card.medium .card-action, .card.large .card-action {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+
+.card.small {
+  height: 300px;
+}
+
+.card.medium {
+  height: 400px;
+}
+
+.card.large {
+  height: 500px;
+}
+
+.card.horizontal {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+}
+
+.card.horizontal.small .card-image, .card.horizontal.medium .card-image, .card.horizontal.large .card-image {
+  height: 100%;
+  max-height: none;
+  overflow: visible;
+}
+
+.card.horizontal.small .card-image img, .card.horizontal.medium .card-image img, .card.horizontal.large .card-image img {
+  height: 100%;
+}
+
+.card.horizontal .card-image {
+  max-width: 50%;
+}
+
+.card.horizontal .card-image img {
+  border-radius: 2px 0 0 2px;
+  max-width: 100%;
+  width: auto;
+}
+
+.card.horizontal .card-stacked {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-flex-direction: column;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  -webkit-flex: 1;
+      -ms-flex: 1;
+          flex: 1;
+  position: relative;
+}
+
+.card.horizontal .card-stacked .card-content {
+  -webkit-flex-grow: 1;
+      -ms-flex-positive: 1;
+          flex-grow: 1;
+}
+
+.card.sticky-action .card-action {
+  z-index: 2;
+}
+
+.card.sticky-action .card-reveal {
+  z-index: 1;
+  padding-bottom: 64px;
+}
+
+.card .card-image {
+  position: relative;
+}
+
+.card .card-image img {
+  display: block;
+  border-radius: 2px 2px 0 0;
+  position: relative;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  width: 100%;
+}
+
+.card .card-image .card-title {
+  color: #fff;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  max-width: 100%;
+  padding: 24px;
+}
+
+.card .card-content {
+  padding: 24px;
+  border-radius: 0 0 2px 2px;
+}
+
+.card .card-content p {
+  margin: 0;
+  color: inherit;
+}
+
+.card .card-content .card-title {
+  display: block;
+  line-height: 32px;
+  margin-bottom: 8px;
+}
+
+.card .card-content .card-title i {
+  line-height: 32px;
+}
+
+.card .card-action {
+  position: relative;
+  background-color: inherit;
+  border-top: 1px solid rgba(160, 160, 160, 0.2);
+  padding: 16px 24px;
+}
+
+.card .card-action a:not(.btn):not(.btn-large):not(.btn-large):not(.btn-floating) {
+  color: #ffab40;
+  margin-right: 24px;
+  transition: color .3s ease;
+  text-transform: uppercase;
+}
+
+.card .card-action a:not(.btn):not(.btn-large):not(.btn-large):not(.btn-floating):hover {
+  color: #ffd8a6;
+}
+
+.card .card-reveal {
+  padding: 24px;
+  position: absolute;
+  background-color: #fff;
+  width: 100%;
+  overflow-y: auto;
+  left: 0;
+  top: 100%;
+  height: 100%;
+  z-index: 3;
+  display: none;
+}
+
+.card .card-reveal .card-title {
+  cursor: pointer;
+  display: block;
+}
+
+#toast-container {
+  display: block;
+  position: fixed;
+  z-index: 10000;
+}
+
+@media only screen and (max-width: 600px) {
+  #toast-container {
+    min-width: 100%;
+    bottom: 0%;
+  }
+}
+
+@media only screen and (min-width: 601px) and (max-width: 992px) {
+  #toast-container {
+    left: 5%;
+    bottom: 7%;
+    max-width: 90%;
+  }
+}
+
+@media only screen and (min-width: 993px) {
+  #toast-container {
+    top: 10%;
+    right: 7%;
+    max-width: 86%;
+  }
+}
+
+.toast {
+  border-radius: 2px;
+  top: 35px;
+  width: auto;
+  clear: both;
+  margin-top: 10px;
+  position: relative;
+  max-width: 100%;
+  height: auto;
+  min-height: 48px;
+  line-height: 1.5em;
+  word-break: break-all;
+  background-color: #323232;
+  padding: 10px 25px;
+  font-size: 1.1rem;
+  font-weight: 300;
+  color: #fff;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-justify-content: space-between;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+}
+
+.toast .btn, .toast .btn-large, .toast .btn-flat {
+  margin: 0;
+  margin-left: 3rem;
+}
+
+.toast.rounded {
+  border-radius: 24px;
+}
+
+@media only screen and (max-width: 600px) {
+  .toast {
+    width: 100%;
+    border-radius: 0;
+  }
+}
+
+@media only screen and (min-width: 601px) and (max-width: 992px) {
+  .toast {
+    float: left;
+  }
+}
+
+@media only screen and (min-width: 993px) {
+  .toast {
+    float: right;
+  }
+}
+
+.tabs {
+  position: relative;
+  overflow-x: auto;
+  overflow-y: hidden;
+  height: 48px;
+  width: 100%;
+  background-color: #fff;
+  margin: 0 auto;
+  white-space: nowrap;
+}
+
+.tabs.tabs-transparent {
+  background-color: transparent;
+}
+
+.tabs.tabs-transparent .tab a,
+.tabs.tabs-transparent .tab.disabled a,
+.tabs.tabs-transparent .tab.disabled a:hover {
+  color: rgba(255, 255, 255, 0.7);
+}
+
+.tabs.tabs-transparent .tab a:hover,
+.tabs.tabs-transparent .tab a.active {
+  color: #fff;
+}
+
+.tabs.tabs-transparent .indicator {
+  background-color: #fff;
+}
+
+.tabs.tabs-fixed-width {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+}
+
+.tabs.tabs-fixed-width .tab {
+  -webkit-flex-grow: 1;
+  -ms-flex-positive: 1;
+  flex-grow: 1;
+}
+
+.tabs .tab {
+  display: inline-block;
+  text-align: center;
+  line-height: 48px;
+  height: 48px;
+  padding: 0;
+  margin: 0;
+  text-transform: uppercase;
+}
+
+.tabs .tab a {
+  color: rgba(238, 110, 115, 0.7);
+  display: block;
+  width: 100%;
+  height: 100%;
+  padding: 0 24px;
+  font-size: 14px;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  transition: color .28s ease;
+}
+
+.tabs .tab a:hover, .tabs .tab a.active {
+  background-color: transparent;
+  color: #ee6e73;
+}
+
+.tabs .tab.disabled a,
+.tabs .tab.disabled a:hover {
+  color: rgba(238, 110, 115, 0.7);
+  cursor: default;
+}
+
+.tabs .indicator {
+  position: absolute;
+  bottom: 0;
+  height: 2px;
+  background-color: #f6b2b5;
+  will-change: left, right;
+}
+
+@media only screen and (max-width: 992px) {
+  .tabs {
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+  }
+  .tabs .tab {
+    -webkit-flex-grow: 1;
+    -ms-flex-positive: 1;
+    flex-grow: 1;
+  }
+  .tabs .tab a {
+    padding: 0 12px;
+  }
+}
+
+.material-tooltip {
+  padding: 10px 8px;
+  font-size: 1rem;
+  z-index: 2000;
+  background-color: transparent;
+  border-radius: 2px;
+  color: #fff;
+  min-height: 36px;
+  line-height: 120%;
+  opacity: 0;
+  position: absolute;
+  text-align: center;
+  max-width: calc(100% - 4px);
+  overflow: hidden;
+  left: 0;
+  top: 0;
+  pointer-events: none;
+  visibility: hidden;
+}
+
+.backdrop {
+  position: absolute;
+  opacity: 0;
+  height: 7px;
+  width: 14px;
+  border-radius: 0 0 50% 50%;
+  background-color: #323232;
+  z-index: -1;
+  -webkit-transform-origin: 50% 0%;
+          transform-origin: 50% 0%;
+  visibility: hidden;
+}
+
+.btn, .btn-large,
+.btn-flat {
+  border: none;
+  border-radius: 2px;
+  display: inline-block;
+  height: 36px;
+  line-height: 36px;
+  padding: 0 2rem;
+  text-transform: uppercase;
+  vertical-align: middle;
+  -webkit-tap-highlight-color: transparent;
+}
+
+.btn.disabled, .disabled.btn-large,
+.btn-floating.disabled,
+.btn-large.disabled,
+.btn-flat.disabled,
+.btn:disabled,
+.btn-large:disabled,
+.btn-floating:disabled,
+.btn-large:disabled,
+.btn-flat:disabled,
+.btn[disabled],
+[disabled].btn-large,
+.btn-floating[disabled],
+.btn-large[disabled],
+.btn-flat[disabled] {
+  pointer-events: none;
+  background-color: #DFDFDF !important;
+  box-shadow: none;
+  color: #9F9F9F !important;
+  cursor: default;
+}
+
+.btn.disabled:hover, .disabled.btn-large:hover,
+.btn-floating.disabled:hover,
+.btn-large.disabled:hover,
+.btn-flat.disabled:hover,
+.btn:disabled:hover,
+.btn-large:disabled:hover,
+.btn-floating:disabled:hover,
+.btn-large:disabled:hover,
+.btn-flat:disabled:hover,
+.btn[disabled]:hover,
+[disabled].btn-large:hover,
+.btn-floating[disabled]:hover,
+.btn-large[disabled]:hover,
+.btn-flat[disabled]:hover {
+  background-color: #DFDFDF !important;
+  color: #9F9F9F !important;
+}
+
+.btn, .btn-large,
+.btn-floating,
+.btn-large,
+.btn-flat {
+  outline: 0;
+}
+
+.btn i, .btn-large i,
+.btn-floating i,
+.btn-large i,
+.btn-flat i {
+  font-size: 1.3rem;
+  line-height: inherit;
+}
+
+.btn:focus, .btn-large:focus,
+.btn-floating:focus {
+  background-color: #1d7d74;
+}
+
+.btn, .btn-large {
+  text-decoration: none;
+  color: #fff;
+  background-color: #26a69a;
+  text-align: center;
+  letter-spacing: .5px;
+  transition: .2s ease-out;
+  cursor: pointer;
+}
+
+.btn:hover, .btn-large:hover {
+  background-color: #2bbbad;
+}
+
+.btn-floating {
+  display: inline-block;
+  color: #fff;
+  position: relative;
+  overflow: hidden;
+  z-index: 1;
+  width: 40px;
+  height: 40px;
+  line-height: 40px;
+  padding: 0;
+  background-color: #26a69a;
+  border-radius: 50%;
+  transition: .3s;
+  cursor: pointer;
+  vertical-align: middle;
+}
+
+.btn-floating:hover {
+  background-color: #26a69a;
+}
+
+.btn-floating:before {
+  border-radius: 0;
+}
+
+.btn-floating.btn-large {
+  width: 56px;
+  height: 56px;
+}
+
+.btn-floating.btn-large i {
+  line-height: 56px;
+}
+
+.btn-floating.halfway-fab {
+  position: absolute;
+  right: 24px;
+  bottom: 0;
+  -webkit-transform: translateY(50%);
+          transform: translateY(50%);
+}
+
+.btn-floating.halfway-fab.left {
+  right: auto;
+  left: 24px;
+}
+
+.btn-floating i {
+  width: inherit;
+  display: inline-block;
+  text-align: center;
+  color: #fff;
+  font-size: 1.6rem;
+  line-height: 40px;
+}
+
+button.btn-floating {
+  border: none;
+}
+
+.fixed-action-btn {
+  position: fixed;
+  right: 23px;
+  bottom: 23px;
+  padding-top: 15px;
+  margin-bottom: 0;
+  z-index: 998;
+}
+
+.fixed-action-btn.active ul {
+  visibility: visible;
+}
+
+.fixed-action-btn.horizontal {
+  padding: 0 0 0 15px;
+}
+
+.fixed-action-btn.horizontal ul {
+  text-align: right;
+  right: 64px;
+  top: 50%;
+  -webkit-transform: translateY(-50%);
+          transform: translateY(-50%);
+  height: 100%;
+  left: auto;
+  width: 500px;
+  /*width 100% only goes to width of button container */
+}
+
+.fixed-action-btn.horizontal ul li {
+  display: inline-block;
+  margin: 15px 15px 0 0;
+}
+
+.fixed-action-btn.toolbar {
+  padding: 0;
+  height: 56px;
+}
+
+.fixed-action-btn.toolbar.active > a i {
+  opacity: 0;
+}
+
+.fixed-action-btn.toolbar ul {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  top: 0;
+  bottom: 0;
+}
+
+.fixed-action-btn.toolbar ul li {
+  -webkit-flex: 1;
+      -ms-flex: 1;
+          flex: 1;
+  display: inline-block;
+  margin: 0;
+  height: 100%;
+  transition: none;
+}
+
+.fixed-action-btn.toolbar ul li a {
+  display: block;
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-color: transparent;
+  box-shadow: none;
+  color: #fff;
+  line-height: 56px;
+  z-index: 1;
+}
+
+.fixed-action-btn.toolbar ul li a i {
+  line-height: inherit;
+}
+
+.fixed-action-btn ul {
+  left: 0;
+  right: 0;
+  text-align: center;
+  position: absolute;
+  bottom: 64px;
+  margin: 0;
+  visibility: hidden;
+}
+
+.fixed-action-btn ul li {
+  margin-bottom: 15px;
+}
+
+.fixed-action-btn ul a.btn-floating {
+  opacity: 0;
+}
+
+.fixed-action-btn .fab-backdrop {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: -1;
+  width: 40px;
+  height: 40px;
+  background-color: #26a69a;
+  border-radius: 50%;
+  -webkit-transform: scale(0);
+          transform: scale(0);
+}
+
+.btn-flat {
+  box-shadow: none;
+  background-color: transparent;
+  color: #343434;
+  cursor: pointer;
+  transition: background-color .2s;
+}
+
+.btn-flat:focus, .btn-flat:active {
+  background-color: transparent;
+}
+
+.btn-flat:focus, .btn-flat:hover {
+  background-color: rgba(0, 0, 0, 0.1);
+  box-shadow: none;
+}
+
+.btn-flat:active {
+  background-color: rgba(0, 0, 0, 0.2);
+}
+
+.btn-flat.disabled {
+  background-color: transparent !important;
+  color: #b3b3b3 !important;
+  cursor: default;
+}
+
+.btn-large {
+  height: 54px;
+  line-height: 54px;
+}
+
+.btn-large i {
+  font-size: 1.6rem;
+}
+
+.btn-block {
+  display: block;
+}
+
+.dropdown-content {
+  background-color: #fff;
+  margin: 0;
+  display: none;
+  min-width: 100px;
+  max-height: 650px;
+  overflow-y: auto;
+  opacity: 0;
+  position: absolute;
+  z-index: 999;
+  will-change: width, height;
+}
+
+.dropdown-content li {
+  clear: both;
+  color: rgba(0, 0, 0, 0.87);
+  cursor: pointer;
+  min-height: 50px;
+  line-height: 1.5rem;
+  width: 100%;
+  text-align: left;
+  text-transform: none;
+}
+
+.dropdown-content li:hover, .dropdown-content li.active, .dropdown-content li.selected {
+  background-color: #eee;
+}
+
+.dropdown-content li.active.selected {
+  background-color: #e1e1e1;
+}
+
+.dropdown-content li.divider {
+  min-height: 0;
+  height: 1px;
+}
+
+.dropdown-content li > a, .dropdown-content li > span {
+  font-size: 16px;
+  color: #26a69a;
+  display: block;
+  line-height: 22px;
+  padding: 14px 16px;
+}
+
+.dropdown-content li > span > label {
+  top: 1px;
+  left: 0;
+  height: 18px;
+}
+
+.dropdown-content li > a > i {
+  height: inherit;
+  line-height: inherit;
+}
+
+.input-field.col .dropdown-content [type="checkbox"] + label {
+  top: 1px;
+  left: 0;
+  height: 18px;
+}
+
+/*!
+ * Waves v0.6.0
+ * http://fian.my.id/Waves
+ *
+ * Copyright 2014 Alfiana E. Sibuea and other contributors
+ * Released under the MIT license
+ * https://github.com/fians/Waves/blob/master/LICENSE
+ */
+.waves-effect {
+  position: relative;
+  cursor: pointer;
+  display: inline-block;
+  overflow: hidden;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+  -webkit-tap-highlight-color: transparent;
+  vertical-align: middle;
+  z-index: 1;
+  transition: .3s ease-out;
+}
+
+.waves-effect .waves-ripple {
+  position: absolute;
+  border-radius: 50%;
+  width: 20px;
+  height: 20px;
+  margin-top: -10px;
+  margin-left: -10px;
+  opacity: 0;
+  background: rgba(0, 0, 0, 0.2);
+  transition: all 0.7s ease-out;
+  transition-property: opacity, -webkit-transform;
+  transition-property: transform, opacity;
+  transition-property: transform, opacity, -webkit-transform;
+  -webkit-transform: scale(0);
+          transform: scale(0);
+  pointer-events: none;
+}
+
+.waves-effect.waves-light .waves-ripple {
+  background-color: rgba(255, 255, 255, 0.45);
+}
+
+.waves-effect.waves-red .waves-ripple {
+  background-color: rgba(244, 67, 54, 0.7);
+}
+
+.waves-effect.waves-yellow .waves-ripple {
+  background-color: rgba(255, 235, 59, 0.7);
+}
+
+.waves-effect.waves-orange .waves-ripple {
+  background-color: rgba(255, 152, 0, 0.7);
+}
+
+.waves-effect.waves-purple .waves-ripple {
+  background-color: rgba(156, 39, 176, 0.7);
+}
+
+.waves-effect.waves-green .waves-ripple {
+  background-color: rgba(76, 175, 80, 0.7);
+}
+
+.waves-effect.waves-teal .waves-ripple {
+  background-color: rgba(0, 150, 136, 0.7);
+}
+
+.waves-effect input[type="button"], .waves-effect input[type="reset"], .waves-effect input[type="submit"] {
+  border: 0;
+  font-style: normal;
+  font-size: inherit;
+  text-transform: inherit;
+  background: none;
+}
+
+.waves-effect img {
+  position: relative;
+  z-index: -1;
+}
+
+.waves-notransition {
+  transition: none !important;
+}
+
+.waves-circle {
+  -webkit-transform: translateZ(0);
+          transform: translateZ(0);
+  -webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%);
+}
+
+.waves-input-wrapper {
+  border-radius: 0.2em;
+  vertical-align: bottom;
+}
+
+.waves-input-wrapper .waves-button-input {
+  position: relative;
+  top: 0;
+  left: 0;
+  z-index: 1;
+}
+
+.waves-circle {
+  text-align: center;
+  width: 2.5em;
+  height: 2.5em;
+  line-height: 2.5em;
+  border-radius: 50%;
+  -webkit-mask-image: none;
+}
+
+.waves-block {
+  display: block;
+}
+
+/* Firefox Bug: link not triggered */
+.waves-effect .waves-ripple {
+  z-index: -1;
+}
+
+.modal {
+  display: none;
+  position: fixed;
+  left: 0;
+  right: 0;
+  background-color: #fafafa;
+  padding: 0;
+  max-height: 70%;
+  width: 55%;
+  margin: auto;
+  overflow-y: auto;
+  border-radius: 2px;
+  will-change: top, opacity;
+}
+
+@media only screen and (max-width: 992px) {
+  .modal {
+    width: 80%;
+  }
+}
+
+.modal h1, .modal h2, .modal h3, .modal h4 {
+  margin-top: 0;
+}
+
+.modal .modal-content {
+  padding: 24px;
+}
+
+.modal .modal-close {
+  cursor: pointer;
+}
+
+.modal .modal-footer {
+  border-radius: 0 0 2px 2px;
+  background-color: #fafafa;
+  padding: 4px 6px;
+  height: 56px;
+  width: 100%;
+}
+
+.modal .modal-footer .btn, .modal .modal-footer .btn-large, .modal .modal-footer .btn-flat {
+  float: right;
+  margin: 6px 0;
+}
+
+.modal-overlay {
+  position: fixed;
+  z-index: 999;
+  top: -100px;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  height: 125%;
+  width: 100%;
+  background: #000;
+  display: none;
+  will-change: opacity;
+}
+
+.modal.modal-fixed-footer {
+  padding: 0;
+  height: 70%;
+}
+
+.modal.modal-fixed-footer .modal-content {
+  position: absolute;
+  height: calc(100% - 56px);
+  max-height: 100%;
+  width: 100%;
+  overflow-y: auto;
+}
+
+.modal.modal-fixed-footer .modal-footer {
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+  position: absolute;
+  bottom: 0;
+}
+
+.modal.bottom-sheet {
+  top: auto;
+  bottom: -100%;
+  margin: 0;
+  width: 100%;
+  max-height: 45%;
+  border-radius: 0;
+  will-change: bottom, opacity;
+}
+
+.collapsible {
+  border-top: 1px solid #ddd;
+  border-right: 1px solid #ddd;
+  border-left: 1px solid #ddd;
+  margin: 0.5rem 0 1rem 0;
+}
+
+.collapsible-header {
+  display: block;
+  cursor: pointer;
+  min-height: 3rem;
+  line-height: 3rem;
+  padding: 0 1rem;
+  background-color: #fff;
+  border-bottom: 1px solid #ddd;
+}
+
+.collapsible-header i {
+  width: 2rem;
+  font-size: 1.6rem;
+  line-height: 3rem;
+  display: block;
+  float: left;
+  text-align: center;
+  margin-right: 1rem;
+}
+
+.collapsible-body {
+  display: none;
+  border-bottom: 1px solid #ddd;
+  box-sizing: border-box;
+  padding: 2rem;
+}
+
+.side-nav .collapsible,
+.side-nav.fixed .collapsible {
+  border: none;
+  box-shadow: none;
+}
+
+.side-nav .collapsible li,
+.side-nav.fixed .collapsible li {
+  padding: 0;
+}
+
+.side-nav .collapsible-header,
+.side-nav.fixed .collapsible-header {
+  background-color: transparent;
+  border: none;
+  line-height: inherit;
+  height: inherit;
+  padding: 0 16px;
+}
+
+.side-nav .collapsible-header:hover,
+.side-nav.fixed .collapsible-header:hover {
+  background-color: rgba(0, 0, 0, 0.05);
+}
+
+.side-nav .collapsible-header i,
+.side-nav.fixed .collapsible-header i {
+  line-height: inherit;
+}
+
+.side-nav .collapsible-body,
+.side-nav.fixed .collapsible-body {
+  border: 0;
+  background-color: #fff;
+}
+
+.side-nav .collapsible-body li a,
+.side-nav.fixed .collapsible-body li a {
+  padding: 0 23.5px 0 31px;
+}
+
+.collapsible.popout {
+  border: none;
+  box-shadow: none;
+}
+
+.collapsible.popout > li {
+  box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
+  margin: 0 24px;
+  transition: margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+.collapsible.popout > li.active {
+  box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15);
+  margin: 16px 0;
+}
+
+.chip {
+  display: inline-block;
+  height: 32px;
+  font-size: 13px;
+  font-weight: 500;
+  color: rgba(0, 0, 0, 0.6);
+  line-height: 32px;
+  padding: 0 12px;
+  border-radius: 16px;
+  background-color: #e4e4e4;
+  margin-bottom: 5px;
+  margin-right: 5px;
+}
+
+.chip img {
+  float: left;
+  margin: 0 8px 0 -12px;
+  height: 32px;
+  width: 32px;
+  border-radius: 50%;
+}
+
+.chip .close {
+  cursor: pointer;
+  float: right;
+  font-size: 16px;
+  line-height: 32px;
+  padding-left: 8px;
+}
+
+.chips {
+  border: none;
+  border-bottom: 1px solid #9e9e9e;
+  box-shadow: none;
+  margin: 0 0 20px 0;
+  min-height: 45px;
+  outline: none;
+  transition: all .3s;
+}
+
+.chips.focus {
+  border-bottom: 1px solid #26a69a;
+  box-shadow: 0 1px 0 0 #26a69a;
+}
+
+.chips:hover {
+  cursor: text;
+}
+
+.chips .chip.selected {
+  background-color: #26a69a;
+  color: #fff;
+}
+
+.chips .input {
+  background: none;
+  border: 0;
+  color: rgba(0, 0, 0, 0.6);
+  display: inline-block;
+  font-size: 1rem;
+  height: 3rem;
+  line-height: 32px;
+  outline: 0;
+  margin: 0;
+  padding: 0 !important;
+  width: 120px !important;
+}
+
+.chips .input:focus {
+  border: 0 !important;
+  box-shadow: none !important;
+}
+
+.prefix ~ .chips {
+  margin-left: 3rem;
+  width: 92%;
+  width: calc(100% - 3rem);
+}
+
+.chips:empty ~ label {
+  font-size: 0.8rem;
+  -webkit-transform: translateY(-140%);
+          transform: translateY(-140%);
+}
+
+.materialboxed {
+  display: block;
+  cursor: -webkit-zoom-in;
+  cursor: zoom-in;
+  position: relative;
+  transition: opacity .4s;
+  -webkit-backface-visibility: hidden;
+}
+
+.materialboxed:hover:not(.active) {
+  opacity: .8;
+}
+
+.materialboxed.active {
+  cursor: -webkit-zoom-out;
+  cursor: zoom-out;
+}
+
+#materialbox-overlay {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background-color: #292929;
+  z-index: 1000;
+  will-change: opacity;
+}
+
+.materialbox-caption {
+  position: fixed;
+  display: none;
+  color: #fff;
+  line-height: 50px;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  text-align: center;
+  padding: 0% 15%;
+  height: 50px;
+  z-index: 1000;
+  -webkit-font-smoothing: antialiased;
+}
+
+select:focus {
+  outline: 1px solid #c9f3ef;
+}
+
+button:focus {
+  outline: none;
+  background-color: #2ab7a9;
+}
+
+label {
+  font-size: 0.8rem;
+  color: #9e9e9e;
+}
+
+/* Text Inputs + Textarea
+   ========================================================================== */
+/* Style Placeholders */
+::-webkit-input-placeholder {
+  color: #d1d1d1;
+}
+
+:-moz-placeholder {
+  /* Firefox 18- */
+  color: #d1d1d1;
+}
+
+::-moz-placeholder {
+  /* Firefox 19+ */
+  color: #d1d1d1;
+}
+
+:-ms-input-placeholder {
+  color: #d1d1d1;
+}
+
+/* Text inputs */
+input:not([type]),
+input[type=text],
+input[type=password],
+input[type=email],
+input[type=url],
+input[type=time],
+input[type=date],
+input[type=datetime],
+input[type=datetime-local],
+input[type=tel],
+input[type=number],
+input[type=search],
+textarea.materialize-textarea {
+  background-color: transparent;
+  border: none;
+  border-bottom: 1px solid #9e9e9e;
+  border-radius: 0;
+  outline: none;
+  height: 3rem;
+  width: 100%;
+  font-size: 1rem;
+  margin: 0 0 20px 0;
+  padding: 0;
+  box-shadow: none;
+  box-sizing: content-box;
+  transition: all 0.3s;
+}
+
+input:not([type]):disabled, input:not([type])[readonly="readonly"],
+input[type=text]:disabled,
+input[type=text][readonly="readonly"],
+input[type=password]:disabled,
+input[type=password][readonly="readonly"],
+input[type=email]:disabled,
+input[type=email][readonly="readonly"],
+input[type=url]:disabled,
+input[type=url][readonly="readonly"],
+input[type=time]:disabled,
+input[type=time][readonly="readonly"],
+input[type=date]:disabled,
+input[type=date][readonly="readonly"],
+input[type=datetime]:disabled,
+input[type=datetime][readonly="readonly"],
+input[type=datetime-local]:disabled,
+input[type=datetime-local][readonly="readonly"],
+input[type=tel]:disabled,
+input[type=tel][readonly="readonly"],
+input[type=number]:disabled,
+input[type=number][readonly="readonly"],
+input[type=search]:disabled,
+input[type=search][readonly="readonly"],
+textarea.materialize-textarea:disabled,
+textarea.materialize-textarea[readonly="readonly"] {
+  color: rgba(0, 0, 0, 0.26);
+  border-bottom: 1px dotted rgba(0, 0, 0, 0.26);
+}
+
+input:not([type]):disabled + label,
+input:not([type])[readonly="readonly"] + label,
+input[type=text]:disabled + label,
+input[type=text][readonly="readonly"] + label,
+input[type=password]:disabled + label,
+input[type=password][readonly="readonly"] + label,
+input[type=email]:disabled + label,
+input[type=email][readonly="readonly"] + label,
+input[type=url]:disabled + label,
+input[type=url][readonly="readonly"] + label,
+input[type=time]:disabled + label,
+input[type=time][readonly="readonly"] + label,
+input[type=date]:disabled + label,
+input[type=date][readonly="readonly"] + label,
+input[type=datetime]:disabled + label,
+input[type=datetime][readonly="readonly"] + label,
+input[type=datetime-local]:disabled + label,
+input[type=datetime-local][readonly="readonly"] + label,
+input[type=tel]:disabled + label,
+input[type=tel][readonly="readonly"] + label,
+input[type=number]:disabled + label,
+input[type=number][readonly="readonly"] + label,
+input[type=search]:disabled + label,
+input[type=search][readonly="readonly"] + label,
+textarea.materialize-textarea:disabled + label,
+textarea.materialize-textarea[readonly="readonly"] + label {
+  color: rgba(0, 0, 0, 0.26);
+}
+
+input:not([type]):focus:not([readonly]),
+input[type=text]:focus:not([readonly]),
+input[type=password]:focus:not([readonly]),
+input[type=email]:focus:not([readonly]),
+input[type=url]:focus:not([readonly]),
+input[type=time]:focus:not([readonly]),
+input[type=date]:focus:not([readonly]),
+input[type=datetime]:focus:not([readonly]),
+input[type=datetime-local]:focus:not([readonly]),
+input[type=tel]:focus:not([readonly]),
+input[type=number]:focus:not([readonly]),
+input[type=search]:focus:not([readonly]),
+textarea.materialize-textarea:focus:not([readonly]) {
+  border-bottom: 1px solid #26a69a;
+  box-shadow: 0 1px 0 0 #26a69a;
+}
+
+input:not([type]):focus:not([readonly]) + label,
+input[type=text]:focus:not([readonly]) + label,
+input[type=password]:focus:not([readonly]) + label,
+input[type=email]:focus:not([readonly]) + label,
+input[type=url]:focus:not([readonly]) + label,
+input[type=time]:focus:not([readonly]) + label,
+input[type=date]:focus:not([readonly]) + label,
+input[type=datetime]:focus:not([readonly]) + label,
+input[type=datetime-local]:focus:not([readonly]) + label,
+input[type=tel]:focus:not([readonly]) + label,
+input[type=number]:focus:not([readonly]) + label,
+input[type=search]:focus:not([readonly]) + label,
+textarea.materialize-textarea:focus:not([readonly]) + label {
+  color: #26a69a;
+}
+
+input:not([type]).valid, input:not([type]):focus.valid,
+input[type=text].valid,
+input[type=text]:focus.valid,
+input[type=password].valid,
+input[type=password]:focus.valid,
+input[type=email].valid,
+input[type=email]:focus.valid,
+input[type=url].valid,
+input[type=url]:focus.valid,
+input[type=time].valid,
+input[type=time]:focus.valid,
+input[type=date].valid,
+input[type=date]:focus.valid,
+input[type=datetime].valid,
+input[type=datetime]:focus.valid,
+input[type=datetime-local].valid,
+input[type=datetime-local]:focus.valid,
+input[type=tel].valid,
+input[type=tel]:focus.valid,
+input[type=number].valid,
+input[type=number]:focus.valid,
+input[type=search].valid,
+input[type=search]:focus.valid,
+textarea.materialize-textarea.valid,
+textarea.materialize-textarea:focus.valid {
+  border-bottom: 1px solid #4CAF50;
+  box-shadow: 0 1px 0 0 #4CAF50;
+}
+
+input:not([type]).valid + label:after,
+input:not([type]):focus.valid + label:after,
+input[type=text].valid + label:after,
+input[type=text]:focus.valid + label:after,
+input[type=password].valid + label:after,
+input[type=password]:focus.valid + label:after,
+input[type=email].valid + label:after,
+input[type=email]:focus.valid + label:after,
+input[type=url].valid + label:after,
+input[type=url]:focus.valid + label:after,
+input[type=time].valid + label:after,
+input[type=time]:focus.valid + label:after,
+input[type=date].valid + label:after,
+input[type=date]:focus.valid + label:after,
+input[type=datetime].valid + label:after,
+input[type=datetime]:focus.valid + label:after,
+input[type=datetime-local].valid + label:after,
+input[type=datetime-local]:focus.valid + label:after,
+input[type=tel].valid + label:after,
+input[type=tel]:focus.valid + label:after,
+input[type=number].valid + label:after,
+input[type=number]:focus.valid + label:after,
+input[type=search].valid + label:after,
+input[type=search]:focus.valid + label:after,
+textarea.materialize-textarea.valid + label:after,
+textarea.materialize-textarea:focus.valid + label:after {
+  content: attr(data-success);
+  color: #4CAF50;
+  opacity: 1;
+}
+
+input:not([type]).invalid, input:not([type]):focus.invalid,
+input[type=text].invalid,
+input[type=text]:focus.invalid,
+input[type=password].invalid,
+input[type=password]:focus.invalid,
+input[type=email].invalid,
+input[type=email]:focus.invalid,
+input[type=url].invalid,
+input[type=url]:focus.invalid,
+input[type=time].invalid,
+input[type=time]:focus.invalid,
+input[type=date].invalid,
+input[type=date]:focus.invalid,
+input[type=datetime].invalid,
+input[type=datetime]:focus.invalid,
+input[type=datetime-local].invalid,
+input[type=datetime-local]:focus.invalid,
+input[type=tel].invalid,
+input[type=tel]:focus.invalid,
+input[type=number].invalid,
+input[type=number]:focus.invalid,
+input[type=search].invalid,
+input[type=search]:focus.invalid,
+textarea.materialize-textarea.invalid,
+textarea.materialize-textarea:focus.invalid {
+  border-bottom: 1px solid #F44336;
+  box-shadow: 0 1px 0 0 #F44336;
+}
+
+input:not([type]).invalid + label:after,
+input:not([type]):focus.invalid + label:after,
+input[type=text].invalid + label:after,
+input[type=text]:focus.invalid + label:after,
+input[type=password].invalid + label:after,
+input[type=password]:focus.invalid + label:after,
+input[type=email].invalid + label:after,
+input[type=email]:focus.invalid + label:after,
+input[type=url].invalid + label:after,
+input[type=url]:focus.invalid + label:after,
+input[type=time].invalid + label:after,
+input[type=time]:focus.invalid + label:after,
+input[type=date].invalid + label:after,
+input[type=date]:focus.invalid + label:after,
+input[type=datetime].invalid + label:after,
+input[type=datetime]:focus.invalid + label:after,
+input[type=datetime-local].invalid + label:after,
+input[type=datetime-local]:focus.invalid + label:after,
+input[type=tel].invalid + label:after,
+input[type=tel]:focus.invalid + label:after,
+input[type=number].invalid + label:after,
+input[type=number]:focus.invalid + label:after,
+input[type=search].invalid + label:after,
+input[type=search]:focus.invalid + label:after,
+textarea.materialize-textarea.invalid + label:after,
+textarea.materialize-textarea:focus.invalid + label:after {
+  content: attr(data-error);
+  color: #F44336;
+  opacity: 1;
+}
+
+input:not([type]).validate + label,
+input[type=text].validate + label,
+input[type=password].validate + label,
+input[type=email].validate + label,
+input[type=url].validate + label,
+input[type=time].validate + label,
+input[type=date].validate + label,
+input[type=datetime].validate + label,
+input[type=datetime-local].validate + label,
+input[type=tel].validate + label,
+input[type=number].validate + label,
+input[type=search].validate + label,
+textarea.materialize-textarea.validate + label {
+  width: 100%;
+  pointer-events: none;
+}
+
+input:not([type]) + label:after,
+input[type=text] + label:after,
+input[type=password] + label:after,
+input[type=email] + label:after,
+input[type=url] + label:after,
+input[type=time] + label:after,
+input[type=date] + label:after,
+input[type=datetime] + label:after,
+input[type=datetime-local] + label:after,
+input[type=tel] + label:after,
+input[type=number] + label:after,
+input[type=search] + label:after,
+textarea.materialize-textarea + label:after {
+  display: block;
+  content: "";
+  position: absolute;
+  top: 60px;
+  opacity: 0;
+  transition: .2s opacity ease-out, .2s color ease-out;
+}
+
+.input-field {
+  position: relative;
+  margin-top: 1rem;
+}
+
+.input-field.inline {
+  display: inline-block;
+  vertical-align: middle;
+  margin-left: 5px;
+}
+
+.input-field.inline input,
+.input-field.inline .select-dropdown {
+  margin-bottom: 1rem;
+}
+
+.input-field.col label {
+  left: 0.75rem;
+}
+
+.input-field.col .prefix ~ label,
+.input-field.col .prefix ~ .validate ~ label {
+  width: calc(100% - 3rem - 1.5rem);
+}
+
+.input-field label {
+  color: #9e9e9e;
+  position: absolute;
+  top: 0.8rem;
+  left: 0;
+  font-size: 1rem;
+  cursor: text;
+  transition: .2s ease-out;
+}
+
+.input-field label:not(.label-icon).active {
+  font-size: 0.8rem;
+  -webkit-transform: translateY(-140%);
+          transform: translateY(-140%);
+}
+
+.input-field .prefix {
+  position: absolute;
+  width: 3rem;
+  font-size: 2rem;
+  transition: color .2s;
+}
+
+.input-field .prefix.active {
+  color: #26a69a;
+}
+
+.input-field .prefix ~ input,
+.input-field .prefix ~ textarea,
+.input-field .prefix ~ label,
+.input-field .prefix ~ .validate ~ label,
+.input-field .prefix ~ .autocomplete-content {
+  margin-left: 3rem;
+  width: 92%;
+  width: calc(100% - 3rem);
+}
+
+.input-field .prefix ~ label {
+  margin-left: 3rem;
+}
+
+@media only screen and (max-width: 992px) {
+  .input-field .prefix ~ input {
+    width: 86%;
+    width: calc(100% - 3rem);
+  }
+}
+
+@media only screen and (max-width: 600px) {
+  .input-field .prefix ~ input {
+    width: 80%;
+    width: calc(100% - 3rem);
+  }
+}
+
+/* Search Field */
+.input-field input[type=search] {
+  display: block;
+  line-height: inherit;
+  padding-left: 4rem;
+  width: calc(100% - 4rem);
+}
+
+.input-field input[type=search]:focus {
+  background-color: #fff;
+  border: 0;
+  box-shadow: none;
+  color: #444;
+}
+
+.input-field input[type=search]:focus + label i,
+.input-field input[type=search]:focus ~ .mdi-navigation-close,
+.input-field input[type=search]:focus ~ .material-icons {
+  color: #444;
+}
+
+.input-field input[type=search] + label {
+  left: 1rem;
+}
+
+.input-field input[type=search] ~ .mdi-navigation-close,
+.input-field input[type=search] ~ .material-icons {
+  position: absolute;
+  top: 0;
+  right: 1rem;
+  color: transparent;
+  cursor: pointer;
+  font-size: 2rem;
+  transition: .3s color;
+}
+
+/* Textarea */
+textarea {
+  width: 100%;
+  height: 3rem;
+  background-color: transparent;
+}
+
+textarea.materialize-textarea {
+  overflow-y: hidden;
+  /* prevents scroll bar flash */
+  padding: .8rem 0 1.6rem 0;
+  /* prevents text jump on Enter keypress */
+  resize: none;
+  min-height: 3rem;
+}
+
+.hiddendiv {
+  display: none;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  /* future version of deprecated 'word-wrap' */
+  padding-top: 1.2rem;
+  /* prevents text jump on Enter keypress */
+}
+
+/* Autocomplete */
+.autocomplete-content {
+  margin-top: -15px;
+  display: block;
+  opacity: 1;
+  position: static;
+}
+
+.autocomplete-content li .highlight {
+  color: #444;
+}
+
+.autocomplete-content li img {
+  height: 40px;
+  width: 40px;
+  margin: 5px 15px;
+}
+
+/* Radio Buttons
+   ========================================================================== */
+[type="radio"]:not(:checked),
+[type="radio"]:checked {
+  position: absolute;
+  left: -9999px;
+  opacity: 0;
+}
+
+[type="radio"]:not(:checked) + label,
+[type="radio"]:checked + label {
+  position: relative;
+  padding-left: 35px;
+  cursor: pointer;
+  display: inline-block;
+  height: 25px;
+  line-height: 25px;
+  font-size: 1rem;
+  transition: .28s ease;
+  /* webkit (konqueror) browsers */
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+}
+
+[type="radio"] + label:before,
+[type="radio"] + label:after {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 0;
+  margin: 4px;
+  width: 16px;
+  height: 16px;
+  z-index: 0;
+  transition: .28s ease;
+}
+
+/* Unchecked styles */
+[type="radio"]:not(:checked) + label:before,
+[type="radio"]:not(:checked) + label:after,
+[type="radio"]:checked + label:before,
+[type="radio"]:checked + label:after,
+[type="radio"].with-gap:checked + label:before,
+[type="radio"].with-gap:checked + label:after {
+  border-radius: 50%;
+}
+
+[type="radio"]:not(:checked) + label:before,
+[type="radio"]:not(:checked) + label:after {
+  border: 2px solid #5a5a5a;
+}
+
+[type="radio"]:not(:checked) + label:after {
+  -webkit-transform: scale(0);
+          transform: scale(0);
+}
+
+/* Checked styles */
+[type="radio"]:checked + label:before {
+  border: 2px solid transparent;
+}
+
+[type="radio"]:checked + label:after,
+[type="radio"].with-gap:checked + label:before,
+[type="radio"].with-gap:checked + label:after {
+  border: 2px solid #26a69a;
+}
+
+[type="radio"]:checked + label:after,
+[type="radio"].with-gap:checked + label:after {
+  background-color: #26a69a;
+}
+
+[type="radio"]:checked + label:after {
+  -webkit-transform: scale(1.02);
+          transform: scale(1.02);
+}
+
+/* Radio With gap */
+[type="radio"].with-gap:checked + label:after {
+  -webkit-transform: scale(0.5);
+          transform: scale(0.5);
+}
+
+/* Focused styles */
+[type="radio"].tabbed:focus + label:before {
+  box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+/* Disabled Radio With gap */
+[type="radio"].with-gap:disabled:checked + label:before {
+  border: 2px solid rgba(0, 0, 0, 0.26);
+}
+
+[type="radio"].with-gap:disabled:checked + label:after {
+  border: none;
+  background-color: rgba(0, 0, 0, 0.26);
+}
+
+/* Disabled style */
+[type="radio"]:disabled:not(:checked) + label:before,
+[type="radio"]:disabled:checked + label:before {
+  background-color: transparent;
+  border-color: rgba(0, 0, 0, 0.26);
+}
+
+[type="radio"]:disabled + label {
+  color: rgba(0, 0, 0, 0.26);
+}
+
+[type="radio"]:disabled:not(:checked) + label:before {
+  border-color: rgba(0, 0, 0, 0.26);
+}
+
+[type="radio"]:disabled:checked + label:after {
+  background-color: rgba(0, 0, 0, 0.26);
+  border-color: #BDBDBD;
+}
+
+/* Checkboxes
+   ========================================================================== */
+/* CUSTOM CSS CHECKBOXES */
+form p {
+  margin-bottom: 10px;
+  text-align: left;
+}
+
+form p:last-child {
+  margin-bottom: 0;
+}
+
+/* Remove default checkbox */
+[type="checkbox"]:not(:checked),
+[type="checkbox"]:checked {
+  position: absolute;
+  left: -9999px;
+  opacity: 0;
+}
+
+[type="checkbox"] {
+  /* checkbox aspect */
+}
+
+[type="checkbox"] + label {
+  position: relative;
+  padding-left: 35px;
+  cursor: pointer;
+  display: inline-block;
+  height: 25px;
+  line-height: 25px;
+  font-size: 1rem;
+  -webkit-user-select: none;
+  /* webkit (safari, chrome) browsers */
+  -moz-user-select: none;
+  /* mozilla browsers */
+  -khtml-user-select: none;
+  /* webkit (konqueror) browsers */
+  -ms-user-select: none;
+  /* IE10+ */
+}
+
+[type="checkbox"] + label:before,
+[type="checkbox"]:not(.filled-in) + label:after {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 18px;
+  height: 18px;
+  z-index: 0;
+  border: 2px solid #5a5a5a;
+  border-radius: 1px;
+  margin-top: 2px;
+  transition: .2s;
+}
+
+[type="checkbox"]:not(.filled-in) + label:after {
+  border: 0;
+  -webkit-transform: scale(0);
+          transform: scale(0);
+}
+
+[type="checkbox"]:not(:checked):disabled + label:before {
+  border: none;
+  background-color: rgba(0, 0, 0, 0.26);
+}
+
+[type="checkbox"].tabbed:focus + label:after {
+  -webkit-transform: scale(1);
+          transform: scale(1);
+  border: 0;
+  border-radius: 50%;
+  box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1);
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+[type="checkbox"]:checked + label:before {
+  top: -4px;
+  left: -5px;
+  width: 12px;
+  height: 22px;
+  border-top: 2px solid transparent;
+  border-left: 2px solid transparent;
+  border-right: 2px solid #26a69a;
+  border-bottom: 2px solid #26a69a;
+  -webkit-transform: rotate(40deg);
+          transform: rotate(40deg);
+  -webkit-backface-visibility: hidden;
+          backface-visibility: hidden;
+  -webkit-transform-origin: 100% 100%;
+          transform-origin: 100% 100%;
+}
+
+[type="checkbox"]:checked:disabled + label:before {
+  border-right: 2px solid rgba(0, 0, 0, 0.26);
+  border-bottom: 2px solid rgba(0, 0, 0, 0.26);
+}
+
+/* Indeterminate checkbox */
+[type="checkbox"]:indeterminate + label:before {
+  top: -11px;
+  left: -12px;
+  width: 10px;
+  height: 22px;
+  border-top: none;
+  border-left: none;
+  border-right: 2px solid #26a69a;
+  border-bottom: none;
+  -webkit-transform: rotate(90deg);
+          transform: rotate(90deg);
+  -webkit-backface-visibility: hidden;
+          backface-visibility: hidden;
+  -webkit-transform-origin: 100% 100%;
+          transform-origin: 100% 100%;
+}
+
+[type="checkbox"]:indeterminate:disabled + label:before {
+  border-right: 2px solid rgba(0, 0, 0, 0.26);
+  background-color: transparent;
+}
+
+[type="checkbox"].filled-in + label:after {
+  border-radius: 2px;
+}
+
+[type="checkbox"].filled-in + label:before,
+[type="checkbox"].filled-in + label:after {
+  content: '';
+  left: 0;
+  position: absolute;
+  /* .1s delay is for check animation */
+  transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;
+  z-index: 1;
+}
+
+[type="checkbox"].filled-in:not(:checked) + label:before {
+  width: 0;
+  height: 0;
+  border: 3px solid transparent;
+  left: 6px;
+  top: 10px;
+  -webkit-transform: rotateZ(37deg);
+  transform: rotateZ(37deg);
+  -webkit-transform-origin: 20% 40%;
+  transform-origin: 100% 100%;
+}
+
+[type="checkbox"].filled-in:not(:checked) + label:after {
+  height: 20px;
+  width: 20px;
+  background-color: transparent;
+  border: 2px solid #5a5a5a;
+  top: 0px;
+  z-index: 0;
+}
+
+[type="checkbox"].filled-in:checked + label:before {
+  top: 0;
+  left: 1px;
+  width: 8px;
+  height: 13px;
+  border-top: 2px solid transparent;
+  border-left: 2px solid transparent;
+  border-right: 2px solid #fff;
+  border-bottom: 2px solid #fff;
+  -webkit-transform: rotateZ(37deg);
+  transform: rotateZ(37deg);
+  -webkit-transform-origin: 100% 100%;
+  transform-origin: 100% 100%;
+}
+
+[type="checkbox"].filled-in:checked + label:after {
+  top: 0;
+  width: 20px;
+  height: 20px;
+  border: 2px solid #26a69a;
+  background-color: #26a69a;
+  z-index: 0;
+}
+
+[type="checkbox"].filled-in.tabbed:focus + label:after {
+  border-radius: 2px;
+  border-color: #5a5a5a;
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+[type="checkbox"].filled-in.tabbed:checked:focus + label:after {
+  border-radius: 2px;
+  background-color: #26a69a;
+  border-color: #26a69a;
+}
+
+[type="checkbox"].filled-in:disabled:not(:checked) + label:before {
+  background-color: transparent;
+  border: 2px solid transparent;
+}
+
+[type="checkbox"].filled-in:disabled:not(:checked) + label:after {
+  border-color: transparent;
+  background-color: #BDBDBD;
+}
+
+[type="checkbox"].filled-in:disabled:checked + label:before {
+  background-color: transparent;
+}
+
+[type="checkbox"].filled-in:disabled:checked + label:after {
+  background-color: #BDBDBD;
+  border-color: #BDBDBD;
+}
+
+/* Switch
+   ========================================================================== */
+.switch,
+.switch * {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+  -ms-user-select: none;
+}
+
+.switch label {
+  cursor: pointer;
+}
+
+.switch label input[type=checkbox] {
+  opacity: 0;
+  width: 0;
+  height: 0;
+}
+
+.switch label input[type=checkbox]:checked + .lever {
+  background-color: #84c7c1;
+}
+
+.switch label input[type=checkbox]:checked + .lever:after {
+  background-color: #26a69a;
+  left: 24px;
+}
+
+.switch label .lever {
+  content: "";
+  display: inline-block;
+  position: relative;
+  width: 40px;
+  height: 15px;
+  background-color: #818181;
+  border-radius: 15px;
+  margin-right: 10px;
+  transition: background 0.3s ease;
+  vertical-align: middle;
+  margin: 0 16px;
+}
+
+.switch label .lever:after {
+  content: "";
+  position: absolute;
+  display: inline-block;
+  width: 21px;
+  height: 21px;
+  background-color: #F1F1F1;
+  border-radius: 21px;
+  box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4);
+  left: -5px;
+  top: -3px;
+  transition: left 0.3s ease, background .3s ease, box-shadow 0.1s ease;
+}
+
+input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after,
+input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after {
+  box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(38, 166, 154, 0.1);
+}
+
+input[type=checkbox]:not(:disabled) ~ .lever:active:after,
+input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after {
+  box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 0, 0, 0.08);
+}
+
+.switch input[type=checkbox][disabled] + .lever {
+  cursor: default;
+}
+
+.switch label input[type=checkbox][disabled] + .lever:after,
+.switch label input[type=checkbox][disabled]:checked + .lever:after {
+  background-color: #BDBDBD;
+}
+
+/* Select Field
+   ========================================================================== */
+select {
+  display: none;
+}
+
+select.browser-default {
+  display: block;
+}
+
+select {
+  background-color: rgba(255, 255, 255, 0.9);
+  width: 100%;
+  padding: 5px;
+  border: 1px solid #f2f2f2;
+  border-radius: 2px;
+  height: 3rem;
+}
+
+.select-label {
+  position: absolute;
+}
+
+.select-wrapper {
+  position: relative;
+}
+
+.select-wrapper input.select-dropdown {
+  position: relative;
+  cursor: pointer;
+  background-color: transparent;
+  border: none;
+  border-bottom: 1px solid #9e9e9e;
+  outline: none;
+  height: 3rem;
+  line-height: 3rem;
+  width: 100%;
+  font-size: 1rem;
+  margin: 0 0 20px 0;
+  padding: 0;
+  display: block;
+}
+
+.select-wrapper span.caret {
+  color: initial;
+  position: absolute;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  height: 10px;
+  margin: auto 0;
+  font-size: 10px;
+  line-height: 10px;
+}
+
+.select-wrapper span.caret.disabled {
+  color: rgba(0, 0, 0, 0.26);
+}
+
+.select-wrapper + label {
+  position: absolute;
+  top: -14px;
+  font-size: 0.8rem;
+}
+
+select:disabled {
+  color: rgba(0, 0, 0, 0.3);
+}
+
+.select-wrapper input.select-dropdown:disabled {
+  color: rgba(0, 0, 0, 0.3);
+  cursor: default;
+  -webkit-user-select: none;
+  /* webkit (safari, chrome) browsers */
+  -moz-user-select: none;
+  /* mozilla browsers */
+  -ms-user-select: none;
+  /* IE10+ */
+  border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+}
+
+.select-wrapper i {
+  color: rgba(0, 0, 0, 0.3);
+}
+
+.select-dropdown li.disabled,
+.select-dropdown li.disabled > span,
+.select-dropdown li.optgroup {
+  color: rgba(0, 0, 0, 0.3);
+  background-color: transparent;
+}
+
+.prefix ~ .select-wrapper {
+  margin-left: 3rem;
+  width: 92%;
+  width: calc(100% - 3rem);
+}
+
+.prefix ~ label {
+  margin-left: 3rem;
+}
+
+.select-dropdown li img {
+  height: 40px;
+  width: 40px;
+  margin: 5px 15px;
+  float: right;
+}
+
+.select-dropdown li.optgroup {
+  border-top: 1px solid #eee;
+}
+
+.select-dropdown li.optgroup.selected > span {
+  color: rgba(0, 0, 0, 0.7);
+}
+
+.select-dropdown li.optgroup > span {
+  color: rgba(0, 0, 0, 0.4);
+}
+
+.select-dropdown li.optgroup ~ li.optgroup-option {
+  padding-left: 1rem;
+}
+
+/* File Input
+   ========================================================================== */
+.file-field {
+  position: relative;
+}
+
+.file-field .file-path-wrapper {
+  overflow: hidden;
+  padding-left: 10px;
+}
+
+.file-field input.file-path {
+  width: 100%;
+}
+
+.file-field .btn, .file-field .btn-large {
+  float: left;
+  height: 3rem;
+  line-height: 3rem;
+}
+
+.file-field span {
+  cursor: pointer;
+}
+
+.file-field input[type=file] {
+  position: absolute;
+  top: 0;
+  right: 0;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  margin: 0;
+  padding: 0;
+  font-size: 20px;
+  cursor: pointer;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+
+/* Range
+   ========================================================================== */
+.range-field {
+  position: relative;
+}
+
+input[type=range],
+input[type=range] + .thumb {
+  cursor: pointer;
+}
+
+input[type=range] {
+  position: relative;
+  background-color: transparent;
+  border: none;
+  outline: none;
+  width: 100%;
+  margin: 15px 0;
+  padding: 0;
+}
+
+input[type=range]:focus {
+  outline: none;
+}
+
+input[type=range] + .thumb {
+  position: absolute;
+  border: none;
+  height: 0;
+  width: 0;
+  border-radius: 50%;
+  background-color: #26a69a;
+  top: 10px;
+  margin-left: -6px;
+  -webkit-transform-origin: 50% 50%;
+          transform-origin: 50% 50%;
+  -webkit-transform: rotate(-45deg);
+          transform: rotate(-45deg);
+}
+
+input[type=range] + .thumb .value {
+  display: block;
+  width: 30px;
+  text-align: center;
+  color: #26a69a;
+  font-size: 0;
+  -webkit-transform: rotate(45deg);
+          transform: rotate(45deg);
+}
+
+input[type=range] + .thumb.active {
+  border-radius: 50% 50% 50% 0;
+}
+
+input[type=range] + .thumb.active .value {
+  color: #fff;
+  margin-left: -1px;
+  margin-top: 8px;
+  font-size: 10px;
+}
+
+input[type=range] {
+  -webkit-appearance: none;
+}
+
+input[type=range]::-webkit-slider-runnable-track {
+  height: 3px;
+  background: #c2c0c2;
+  border: none;
+}
+
+input[type=range]::-webkit-slider-thumb {
+  -webkit-appearance: none;
+  border: none;
+  height: 14px;
+  width: 14px;
+  border-radius: 50%;
+  background-color: #26a69a;
+  -webkit-transform-origin: 50% 50%;
+          transform-origin: 50% 50%;
+  margin: -5px 0 0 0;
+  transition: .3s;
+}
+
+input[type=range]:focus::-webkit-slider-runnable-track {
+  background: #ccc;
+}
+
+input[type=range] {
+  /* fix for FF unable to apply focus style bug  */
+  border: 1px solid white;
+  /*required for proper track sizing in FF*/
+}
+
+input[type=range]::-moz-range-track {
+  height: 3px;
+  background: #ddd;
+  border: none;
+}
+
+input[type=range]::-moz-range-thumb {
+  border: none;
+  height: 14px;
+  width: 14px;
+  border-radius: 50%;
+  background: #26a69a;
+  margin-top: -5px;
+}
+
+input[type=range]:-moz-focusring {
+  outline: 1px solid #fff;
+  outline-offset: -1px;
+}
+
+input[type=range]:focus::-moz-range-track {
+  background: #ccc;
+}
+
+input[type=range]::-ms-track {
+  height: 3px;
+  background: transparent;
+  border-color: transparent;
+  border-width: 6px 0;
+  /*remove default tick marks*/
+  color: transparent;
+}
+
+input[type=range]::-ms-fill-lower {
+  background: #777;
+}
+
+input[type=range]::-ms-fill-upper {
+  background: #ddd;
+}
+
+input[type=range]::-ms-thumb {
+  border: none;
+  height: 14px;
+  width: 14px;
+  border-radius: 50%;
+  background: #26a69a;
+}
+
+input[type=range]:focus::-ms-fill-lower {
+  background: #888;
+}
+
+input[type=range]:focus::-ms-fill-upper {
+  background: #ccc;
+}
+
+/***************
+    Nav List
+***************/
+.table-of-contents.fixed {
+  position: fixed;
+}
+
+.table-of-contents li {
+  padding: 2px 0;
+}
+
+.table-of-contents a {
+  display: inline-block;
+  font-weight: 300;
+  color: #757575;
+  padding-left: 20px;
+  height: 1.5rem;
+  line-height: 1.5rem;
+  letter-spacing: .4;
+  display: inline-block;
+}
+
+.table-of-contents a:hover {
+  color: #a8a8a8;
+  padding-left: 19px;
+  border-left: 1px solid #ee6e73;
+}
+
+.table-of-contents a.active {
+  font-weight: 500;
+  padding-left: 18px;
+  border-left: 2px solid #ee6e73;
+}
+
+.side-nav {
+  position: fixed;
+  width: 300px;
+  left: 0;
+  top: 0;
+  margin: 0;
+  -webkit-transform: translateX(-100%);
+          transform: translateX(-100%);
+  height: 100%;
+  height: calc(100% + 60px);
+  height: -moz-calc(100%);
+  padding-bottom: 60px;
+  background-color: #fff;
+  z-index: 999;
+  overflow-y: auto;
+  will-change: transform;
+  -webkit-backface-visibility: hidden;
+          backface-visibility: hidden;
+  -webkit-transform: translateX(-105%);
+          transform: translateX(-105%);
+}
+
+.side-nav.right-aligned {
+  right: 0;
+  -webkit-transform: translateX(105%);
+          transform: translateX(105%);
+  left: auto;
+  -webkit-transform: translateX(100%);
+          transform: translateX(100%);
+}
+
+.side-nav .collapsible {
+  margin: 0;
+}
+
+.side-nav li {
+  float: none;
+  line-height: 48px;
+}
+
+.side-nav li.active {
+  background-color: rgba(0, 0, 0, 0.05);
+}
+
+.side-nav a {
+  color: rgba(0, 0, 0, 0.87);
+  display: block;
+  font-size: 14px;
+  font-weight: 500;
+  height: 48px;
+  line-height: 48px;
+  padding: 0 32px;
+}
+
+.side-nav a:hover {
+  background-color: rgba(0, 0, 0, 0.05);
+}
+
+.side-nav a.btn, .side-nav a.btn-large, .side-nav a.btn-large, .side-nav a.btn-flat, .side-nav a.btn-floating {
+  margin: 10px 15px;
+}
+
+.side-nav a.btn, .side-nav a.btn-large, .side-nav a.btn-large, .side-nav a.btn-floating {
+  color: #fff;
+}
+
+.side-nav a.btn-flat {
+  color: #343434;
+}
+
+.side-nav a.btn:hover, .side-nav a.btn-large:hover, .side-nav a.btn-large:hover {
+  background-color: #2bbbad;
+}
+
+.side-nav a.btn-floating:hover {
+  background-color: #26a69a;
+}
+
+.side-nav li > a > i,
+.side-nav li > a > [class^="mdi-"], .side-nav li > a > [class*="mdi-"],
+.side-nav li > a > i.material-icons {
+  float: left;
+  height: 48px;
+  line-height: 48px;
+  margin: 0 32px 0 0;
+  width: 24px;
+  color: rgba(0, 0, 0, 0.54);
+}
+
+.side-nav .divider {
+  margin: 8px 0 0 0;
+}
+
+.side-nav .subheader {
+  cursor: initial;
+  pointer-events: none;
+  color: rgba(0, 0, 0, 0.54);
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 48px;
+}
+
+.side-nav .subheader:hover {
+  background-color: transparent;
+}
+
+.side-nav .userView {
+  position: relative;
+  padding: 32px 32px 0;
+  margin-bottom: 8px;
+}
+
+.side-nav .userView > a {
+  height: auto;
+  padding: 0;
+}
+
+.side-nav .userView > a:hover {
+  background-color: transparent;
+}
+
+.side-nav .userView .background {
+  overflow: hidden;
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: -1;
+}
+
+.side-nav .userView .circle, .side-nav .userView .name, .side-nav .userView .email {
+  display: block;
+}
+
+.side-nav .userView .circle {
+  height: 64px;
+  width: 64px;
+}
+
+.side-nav .userView .name,
+.side-nav .userView .email {
+  font-size: 14px;
+  line-height: 24px;
+}
+
+.side-nav .userView .name {
+  margin-top: 16px;
+  font-weight: 500;
+}
+
+.side-nav .userView .email {
+  padding-bottom: 16px;
+  font-weight: 400;
+}
+
+.drag-target {
+  height: 100%;
+  width: 10px;
+  position: fixed;
+  top: 0;
+  z-index: 998;
+}
+
+.side-nav.fixed {
+  left: 0;
+  -webkit-transform: translateX(0);
+          transform: translateX(0);
+  position: fixed;
+}
+
+.side-nav.fixed.right-aligned {
+  right: 0;
+  left: auto;
+}
+
+@media only screen and (max-width: 992px) {
+  .side-nav.fixed {
+    -webkit-transform: translateX(-105%);
+            transform: translateX(-105%);
+  }
+  .side-nav.fixed.right-aligned {
+    -webkit-transform: translateX(105%);
+            transform: translateX(105%);
+  }
+  .side-nav a {
+    padding: 0 16px;
+  }
+  .side-nav .userView {
+    padding: 16px 16px 0;
+  }
+}
+
+.side-nav .collapsible-body > ul:not(.collapsible) > li.active,
+.side-nav.fixed .collapsible-body > ul:not(.collapsible) > li.active {
+  background-color: #ee6e73;
+}
+
+.side-nav .collapsible-body > ul:not(.collapsible) > li.active a,
+.side-nav.fixed .collapsible-body > ul:not(.collapsible) > li.active a {
+  color: #fff;
+}
+
+#sidenav-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 120vh;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 997;
+  will-change: opacity;
+}
+
+/*
+    @license
+    Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+    This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+    The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+    The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+    Code distributed by Google as part of the polymer project is also
+    subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+ */
+/**************************/
+/* STYLES FOR THE SPINNER */
+/**************************/
+/*
+ * Constants:
+ *      STROKEWIDTH = 3px
+ *      ARCSIZE     = 270 degrees (amount of circle the arc takes up)
+ *      ARCTIME     = 1333ms (time it takes to expand and contract arc)
+ *      ARCSTARTROT = 216 degrees (how much the start location of the arc
+ *                                should rotate each time, 216 gives us a
+ *                                5 pointed star shape (it's 360/5 * 3).
+ *                                For a 7 pointed star, we might do
+ *                                360/7 * 3 = 154.286)
+ *      CONTAINERWIDTH = 28px
+ *      SHRINK_TIME = 400ms
+ */
+.preloader-wrapper {
+  display: inline-block;
+  position: relative;
+  width: 48px;
+  height: 48px;
+}
+
+.preloader-wrapper.small {
+  width: 36px;
+  height: 36px;
+}
+
+.preloader-wrapper.big {
+  width: 64px;
+  height: 64px;
+}
+
+.preloader-wrapper.active {
+  /* duration: 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */
+  -webkit-animation: container-rotate 1568ms linear infinite;
+  animation: container-rotate 1568ms linear infinite;
+}
+
+@-webkit-keyframes container-rotate {
+  to {
+    -webkit-transform: rotate(360deg);
+  }
+}
+
+@keyframes container-rotate {
+  to {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+  }
+}
+
+.spinner-layer {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  opacity: 0;
+  border-color: #26a69a;
+}
+
+.spinner-blue,
+.spinner-blue-only {
+  border-color: #4285f4;
+}
+
+.spinner-red,
+.spinner-red-only {
+  border-color: #db4437;
+}
+
+.spinner-yellow,
+.spinner-yellow-only {
+  border-color: #f4b400;
+}
+
+.spinner-green,
+.spinner-green-only {
+  border-color: #0f9d58;
+}
+
+/**
+ * IMPORTANT NOTE ABOUT CSS ANIMATION PROPERTIES (keanulee):
+ *
+ * iOS Safari (tested on iOS 8.1) does not handle animation-delay very well - it doesn't
+ * guarantee that the animation will start _exactly_ after that value. So we avoid using
+ * animation-delay and instead set custom keyframes for each color (as redundant as it
+ * seems).
+ *
+ * We write out each animation in full (instead of separating animation-name,
+ * animation-duration, etc.) because under the polyfill, Safari does not recognize those
+ * specific properties properly, treats them as -webkit-animation, and overrides the
+ * other animation rules. See https://github.com/Polymer/platform/issues/53.
+ */
+.active .spinner-layer.spinner-blue {
+  /* durations: 4 * ARCTIME */
+  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.active .spinner-layer.spinner-red {
+  /* durations: 4 * ARCTIME */
+  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.active .spinner-layer.spinner-yellow {
+  /* durations: 4 * ARCTIME */
+  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.active .spinner-layer.spinner-green {
+  /* durations: 4 * ARCTIME */
+  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.active .spinner-layer,
+.active .spinner-layer.spinner-blue-only,
+.active .spinner-layer.spinner-red-only,
+.active .spinner-layer.spinner-yellow-only,
+.active .spinner-layer.spinner-green-only {
+  /* durations: 4 * ARCTIME */
+  opacity: 1;
+  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+@-webkit-keyframes fill-unfill-rotate {
+  12.5% {
+    -webkit-transform: rotate(135deg);
+  }
+  /* 0.5 * ARCSIZE */
+  25% {
+    -webkit-transform: rotate(270deg);
+  }
+  /* 1   * ARCSIZE */
+  37.5% {
+    -webkit-transform: rotate(405deg);
+  }
+  /* 1.5 * ARCSIZE */
+  50% {
+    -webkit-transform: rotate(540deg);
+  }
+  /* 2   * ARCSIZE */
+  62.5% {
+    -webkit-transform: rotate(675deg);
+  }
+  /* 2.5 * ARCSIZE */
+  75% {
+    -webkit-transform: rotate(810deg);
+  }
+  /* 3   * ARCSIZE */
+  87.5% {
+    -webkit-transform: rotate(945deg);
+  }
+  /* 3.5 * ARCSIZE */
+  to {
+    -webkit-transform: rotate(1080deg);
+  }
+  /* 4   * ARCSIZE */
+}
+
+@keyframes fill-unfill-rotate {
+  12.5% {
+    -webkit-transform: rotate(135deg);
+            transform: rotate(135deg);
+  }
+  /* 0.5 * ARCSIZE */
+  25% {
+    -webkit-transform: rotate(270deg);
+            transform: rotate(270deg);
+  }
+  /* 1   * ARCSIZE */
+  37.5% {
+    -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+  }
+  /* 1.5 * ARCSIZE */
+  50% {
+    -webkit-transform: rotate(540deg);
+            transform: rotate(540deg);
+  }
+  /* 2   * ARCSIZE */
+  62.5% {
+    -webkit-transform: rotate(675deg);
+            transform: rotate(675deg);
+  }
+  /* 2.5 * ARCSIZE */
+  75% {
+    -webkit-transform: rotate(810deg);
+            transform: rotate(810deg);
+  }
+  /* 3   * ARCSIZE */
+  87.5% {
+    -webkit-transform: rotate(945deg);
+            transform: rotate(945deg);
+  }
+  /* 3.5 * ARCSIZE */
+  to {
+    -webkit-transform: rotate(1080deg);
+            transform: rotate(1080deg);
+  }
+  /* 4   * ARCSIZE */
+}
+
+@-webkit-keyframes blue-fade-in-out {
+  from {
+    opacity: 1;
+  }
+  25% {
+    opacity: 1;
+  }
+  26% {
+    opacity: 0;
+  }
+  89% {
+    opacity: 0;
+  }
+  90% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+
+@keyframes blue-fade-in-out {
+  from {
+    opacity: 1;
+  }
+  25% {
+    opacity: 1;
+  }
+  26% {
+    opacity: 0;
+  }
+  89% {
+    opacity: 0;
+  }
+  90% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+
+@-webkit-keyframes red-fade-in-out {
+  from {
+    opacity: 0;
+  }
+  15% {
+    opacity: 0;
+  }
+  25% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 1;
+  }
+  51% {
+    opacity: 0;
+  }
+}
+
+@keyframes red-fade-in-out {
+  from {
+    opacity: 0;
+  }
+  15% {
+    opacity: 0;
+  }
+  25% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 1;
+  }
+  51% {
+    opacity: 0;
+  }
+}
+
+@-webkit-keyframes yellow-fade-in-out {
+  from {
+    opacity: 0;
+  }
+  40% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+  75% {
+    opacity: 1;
+  }
+  76% {
+    opacity: 0;
+  }
+}
+
+@keyframes yellow-fade-in-out {
+  from {
+    opacity: 0;
+  }
+  40% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+  75% {
+    opacity: 1;
+  }
+  76% {
+    opacity: 0;
+  }
+}
+
+@-webkit-keyframes green-fade-in-out {
+  from {
+    opacity: 0;
+  }
+  65% {
+    opacity: 0;
+  }
+  75% {
+    opacity: 1;
+  }
+  90% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
+}
+
+@keyframes green-fade-in-out {
+  from {
+    opacity: 0;
+  }
+  65% {
+    opacity: 0;
+  }
+  75% {
+    opacity: 1;
+  }
+  90% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
+}
+
+/**
+ * Patch the gap that appear between the two adjacent div.circle-clipper while the
+ * spinner is rotating (appears on Chrome 38, Safari 7.1, and IE 11).
+ */
+.gap-patch {
+  position: absolute;
+  top: 0;
+  left: 45%;
+  width: 10%;
+  height: 100%;
+  overflow: hidden;
+  border-color: inherit;
+}
+
+.gap-patch .circle {
+  width: 1000%;
+  left: -450%;
+}
+
+.circle-clipper {
+  display: inline-block;
+  position: relative;
+  width: 50%;
+  height: 100%;
+  overflow: hidden;
+  border-color: inherit;
+}
+
+.circle-clipper .circle {
+  width: 200%;
+  height: 100%;
+  border-width: 3px;
+  /* STROKEWIDTH */
+  border-style: solid;
+  border-color: inherit;
+  border-bottom-color: transparent !important;
+  border-radius: 50%;
+  -webkit-animation: none;
+  animation: none;
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+}
+
+.circle-clipper.left .circle {
+  left: 0;
+  border-right-color: transparent !important;
+  -webkit-transform: rotate(129deg);
+  transform: rotate(129deg);
+}
+
+.circle-clipper.right .circle {
+  left: -100%;
+  border-left-color: transparent !important;
+  -webkit-transform: rotate(-129deg);
+  transform: rotate(-129deg);
+}
+
+.active .circle-clipper.left .circle {
+  /* duration: ARCTIME */
+  -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.active .circle-clipper.right .circle {
+  /* duration: ARCTIME */
+  -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+  animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+@-webkit-keyframes left-spin {
+  from {
+    -webkit-transform: rotate(130deg);
+  }
+  50% {
+    -webkit-transform: rotate(-5deg);
+  }
+  to {
+    -webkit-transform: rotate(130deg);
+  }
+}
+
+@keyframes left-spin {
+  from {
+    -webkit-transform: rotate(130deg);
+            transform: rotate(130deg);
+  }
+  50% {
+    -webkit-transform: rotate(-5deg);
+            transform: rotate(-5deg);
+  }
+  to {
+    -webkit-transform: rotate(130deg);
+            transform: rotate(130deg);
+  }
+}
+
+@-webkit-keyframes right-spin {
+  from {
+    -webkit-transform: rotate(-130deg);
+  }
+  50% {
+    -webkit-transform: rotate(5deg);
+  }
+  to {
+    -webkit-transform: rotate(-130deg);
+  }
+}
+
+@keyframes right-spin {
+  from {
+    -webkit-transform: rotate(-130deg);
+            transform: rotate(-130deg);
+  }
+  50% {
+    -webkit-transform: rotate(5deg);
+            transform: rotate(5deg);
+  }
+  to {
+    -webkit-transform: rotate(-130deg);
+            transform: rotate(-130deg);
+  }
+}
+
+#spinnerContainer.cooldown {
+  /* duration: SHRINK_TIME */
+  -webkit-animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);
+  animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+@-webkit-keyframes fade-out {
+  from {
+    opacity: 1;
+  }
+  to {
+    opacity: 0;
+  }
+}
+
+@keyframes fade-out {
+  from {
+    opacity: 1;
+  }
+  to {
+    opacity: 0;
+  }
+}
+
+.slider {
+  position: relative;
+  height: 400px;
+  width: 100%;
+}
+
+.slider.fullscreen {
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+
+.slider.fullscreen ul.slides {
+  height: 100%;
+}
+
+.slider.fullscreen ul.indicators {
+  z-index: 2;
+  bottom: 30px;
+}
+
+.slider .slides {
+  background-color: #9e9e9e;
+  margin: 0;
+  height: 400px;
+}
+
+.slider .slides li {
+  opacity: 0;
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1;
+  width: 100%;
+  height: inherit;
+  overflow: hidden;
+}
+
+.slider .slides li img {
+  height: 100%;
+  width: 100%;
+  background-size: cover;
+  background-position: center;
+}
+
+.slider .slides li .caption {
+  color: #fff;
+  position: absolute;
+  top: 15%;
+  left: 15%;
+  width: 70%;
+  opacity: 0;
+}
+
+.slider .slides li .caption p {
+  color: #e0e0e0;
+}
+
+.slider .slides li.active {
+  z-index: 2;
+}
+
+.slider .indicators {
+  position: absolute;
+  text-align: center;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  margin: 0;
+}
+
+.slider .indicators .indicator-item {
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+  height: 16px;
+  width: 16px;
+  margin: 0 12px;
+  background-color: #e0e0e0;
+  transition: background-color .3s;
+  border-radius: 50%;
+}
+
+.slider .indicators .indicator-item.active {
+  background-color: #4CAF50;
+}
+
+.carousel {
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+  height: 400px;
+  -webkit-perspective: 500px;
+          perspective: 500px;
+  -webkit-transform-style: preserve-3d;
+          transform-style: preserve-3d;
+  -webkit-transform-origin: 0% 50%;
+          transform-origin: 0% 50%;
+}
+
+.carousel.carousel-slider {
+  top: 0;
+  left: 0;
+  height: 0;
+}
+
+.carousel.carousel-slider .carousel-fixed-item {
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 20px;
+  z-index: 1;
+}
+
+.carousel.carousel-slider .carousel-fixed-item.with-indicators {
+  bottom: 68px;
+}
+
+.carousel.carousel-slider .carousel-item {
+  width: 100%;
+  height: 100%;
+  min-height: 400px;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.carousel.carousel-slider .carousel-item h2 {
+  font-size: 24px;
+  font-weight: 500;
+  line-height: 32px;
+}
+
+.carousel.carousel-slider .carousel-item p {
+  font-size: 15px;
+}
+
+.carousel .carousel-item {
+  display: none;
+  width: 200px;
+  height: 200px;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.carousel .carousel-item img {
+  width: 100%;
+}
+
+.carousel .indicators {
+  position: absolute;
+  text-align: center;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  margin: 0;
+}
+
+.carousel .indicators .indicator-item {
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+  height: 8px;
+  width: 8px;
+  margin: 24px 4px;
+  background-color: rgba(255, 255, 255, 0.5);
+  transition: background-color .3s;
+  border-radius: 50%;
+}
+
+.carousel .indicators .indicator-item.active {
+  background-color: #fff;
+}
+
+/* ==========================================================================
+   $BASE-PICKER
+   ========================================================================== */
+/**
+ * Note: the root picker element should *NOT* be styled more than what's here.
+ */
+.picker {
+  font-size: 16px;
+  text-align: left;
+  line-height: 1.2;
+  color: #000000;
+  position: absolute;
+  z-index: 10000;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+/**
+ * The picker input element.
+ */
+.picker__input {
+  cursor: default;
+}
+
+/**
+ * When the picker is opened, the input element is "activated".
+ */
+.picker__input.picker__input--active {
+  border-color: #0089ec;
+}
+
+/**
+ * The holder is the only "scrollable" top-level container element.
+ */
+.picker__holder {
+  width: 100%;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+}
+
+/*!
+ * Default mobile-first, responsive styling for pickadate.js
+ * Demo: http://amsul.github.io/pickadate.js
+ */
+/**
+ * Note: the root picker element should *NOT* be styled more than what's here.
+ */
+/**
+ * Make the holder and frame fullscreen.
+ */
+.picker__holder,
+.picker__frame {
+  bottom: 0;
+  left: 0;
+  right: 0;
+  top: 100%;
+}
+
+/**
+ * The holder should overlay the entire screen.
+ */
+.picker__holder {
+  position: fixed;
+  transition: background 0.15s ease-out, top 0s 0.15s;
+  -webkit-backface-visibility: hidden;
+}
+
+/**
+ * The frame that bounds the box contents of the picker.
+ */
+.picker__frame {
+  position: absolute;
+  margin: 0 auto;
+  min-width: 256px;
+  width: 300px;
+  max-height: 350px;
+  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+  filter: alpha(opacity=0);
+  -moz-opacity: 0;
+  opacity: 0;
+  transition: all 0.15s ease-out;
+}
+
+@media (min-height: 28.875em) {
+  .picker__frame {
+    overflow: visible;
+    top: auto;
+    bottom: -100%;
+    max-height: 80%;
+  }
+}
+
+@media (min-height: 40.125em) {
+  .picker__frame {
+    margin-bottom: 7.5%;
+  }
+}
+
+/**
+ * The wrapper sets the stage to vertically align the box contents.
+ */
+.picker__wrap {
+  display: table;
+  width: 100%;
+  height: 100%;
+}
+
+@media (min-height: 28.875em) {
+  .picker__wrap {
+    display: block;
+  }
+}
+
+/**
+ * The box contains all the picker contents.
+ */
+.picker__box {
+  background: #ffffff;
+  display: table-cell;
+  vertical-align: middle;
+}
+
+@media (min-height: 28.875em) {
+  .picker__box {
+    display: block;
+    border: 1px solid #777777;
+    border-top-color: #898989;
+    border-bottom-width: 0;
+    border-radius: 5px 5px 0 0;
+    box-shadow: 0 12px 36px 16px rgba(0, 0, 0, 0.24);
+  }
+}
+
+/**
+ * When the picker opens...
+ */
+.picker--opened .picker__holder {
+  top: 0;
+  background: transparent;
+  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E000000,endColorstr=#1E000000)";
+  zoom: 1;
+  background: rgba(0, 0, 0, 0.32);
+  transition: background 0.15s ease-out;
+}
+
+.picker--opened .picker__frame {
+  top: 0;
+  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+  filter: alpha(opacity=100);
+  -moz-opacity: 1;
+  opacity: 1;
+}
+
+@media (min-height: 35.875em) {
+  .picker--opened .picker__frame {
+    top: 10%;
+    bottom: auto;
+  }
+}
+
+/**
+ * For `large` screens, transform into an inline picker.
+ */
+/* ==========================================================================
+   CUSTOM MATERIALIZE STYLES
+   ========================================================================== */
+.picker__input.picker__input--active {
+  border-color: #E3F2FD;
+}
+
+.picker__frame {
+  margin: 0 auto;
+  max-width: 325px;
+}
+
+@media (min-height: 38.875em) {
+  .picker--opened .picker__frame {
+    top: 10%;
+    bottom: auto;
+  }
+}
+
+/* ==========================================================================
+   $BASE-DATE-PICKER
+   ========================================================================== */
+/**
+ * The picker box.
+ */
+.picker__box {
+  padding: 0 1em;
+}
+
+/**
+ * The header containing the month and year stuff.
+ */
+.picker__header {
+  text-align: center;
+  position: relative;
+  margin-top: .75em;
+}
+
+/**
+ * The month and year labels.
+ */
+.picker__month,
+.picker__year {
+  display: inline-block;
+  margin-left: .25em;
+  margin-right: .25em;
+}
+
+/**
+ * The month and year selectors.
+ */
+.picker__select--month,
+.picker__select--year {
+  height: 2em;
+  padding: 0;
+  margin-left: .25em;
+  margin-right: .25em;
+}
+
+.picker__select--month.browser-default {
+  display: inline;
+  background-color: #FFFFFF;
+  width: 40%;
+}
+
+.picker__select--year.browser-default {
+  display: inline;
+  background-color: #FFFFFF;
+  width: 26%;
+}
+
+.picker__select--month:focus,
+.picker__select--year:focus {
+  border-color: rgba(0, 0, 0, 0.05);
+}
+
+/**
+ * The month navigation buttons.
+ */
+.picker__nav--prev,
+.picker__nav--next {
+  position: absolute;
+  padding: .5em 1.25em;
+  width: 1em;
+  height: 1em;
+  box-sizing: content-box;
+  top: -0.25em;
+}
+
+.picker__nav--prev {
+  left: -1em;
+  padding-right: 1.25em;
+}
+
+.picker__nav--next {
+  right: -1em;
+  padding-left: 1.25em;
+}
+
+.picker__nav--disabled,
+.picker__nav--disabled:hover,
+.picker__nav--disabled:before,
+.picker__nav--disabled:before:hover {
+  cursor: default;
+  background: none;
+  border-right-color: #f5f5f5;
+  border-left-color: #f5f5f5;
+}
+
+/**
+ * The calendar table of dates
+ */
+.picker__table {
+  text-align: center;
+  border-collapse: collapse;
+  border-spacing: 0;
+  table-layout: fixed;
+  font-size: 1rem;
+  width: 100%;
+  margin-top: .75em;
+  margin-bottom: .5em;
+}
+
+.picker__table th, .picker__table td {
+  text-align: center;
+}
+
+.picker__table td {
+  margin: 0;
+  padding: 0;
+}
+
+/**
+ * The weekday labels
+ */
+.picker__weekday {
+  width: 14.285714286%;
+  font-size: .75em;
+  padding-bottom: .25em;
+  color: #999999;
+  font-weight: 500;
+  /* Increase the spacing a tad */
+}
+
+@media (min-height: 33.875em) {
+  .picker__weekday {
+    padding-bottom: .5em;
+  }
+}
+
+/**
+ * The days on the calendar
+ */
+.picker__day--today {
+  position: relative;
+  color: #595959;
+  letter-spacing: -.3;
+  padding: .75rem 0;
+  font-weight: 400;
+  border: 1px solid transparent;
+}
+
+.picker__day--disabled:before {
+  border-top-color: #aaaaaa;
+}
+
+.picker__day--infocus:hover {
+  cursor: pointer;
+  color: #000;
+  font-weight: 500;
+}
+
+.picker__day--outfocus {
+  display: none;
+  padding: .75rem 0;
+  color: #fff;
+}
+
+.picker__day--outfocus:hover {
+  cursor: pointer;
+  color: #dddddd;
+  font-weight: 500;
+}
+
+.picker__day--highlighted:hover,
+.picker--focused .picker__day--highlighted {
+  cursor: pointer;
+}
+
+.picker__day--selected,
+.picker__day--selected:hover,
+.picker--focused .picker__day--selected {
+  border-radius: 50%;
+  -webkit-transform: scale(0.75);
+          transform: scale(0.75);
+  background: #0089ec;
+  color: #ffffff;
+}
+
+.picker__day--disabled,
+.picker__day--disabled:hover,
+.picker--focused .picker__day--disabled {
+  background: #f5f5f5;
+  border-color: #f5f5f5;
+  color: #dddddd;
+  cursor: default;
+}
+
+.picker__day--highlighted.picker__day--disabled,
+.picker__day--highlighted.picker__day--disabled:hover {
+  background: #bbbbbb;
+}
+
+/**
+ * The footer containing the "today", "clear", and "close" buttons.
+ */
+.picker__footer {
+  text-align: center;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-justify-content: space-between;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+}
+
+.picker__button--today,
+.picker__button--clear,
+.picker__button--close {
+  border: 1px solid #ffffff;
+  background: #ffffff;
+  font-size: .8em;
+  padding: .66em 0;
+  font-weight: bold;
+  width: 33%;
+  display: inline-block;
+  vertical-align: bottom;
+}
+
+.picker__button--today:hover,
+.picker__button--clear:hover,
+.picker__button--close:hover {
+  cursor: pointer;
+  color: #000000;
+  background: #b1dcfb;
+  border-bottom-color: #b1dcfb;
+}
+
+.picker__button--today:focus,
+.picker__button--clear:focus,
+.picker__button--close:focus {
+  background: #b1dcfb;
+  border-color: rgba(0, 0, 0, 0.05);
+  outline: none;
+}
+
+.picker__button--today:before,
+.picker__button--clear:before,
+.picker__button--close:before {
+  position: relative;
+  display: inline-block;
+  height: 0;
+}
+
+.picker__button--today:before,
+.picker__button--clear:before {
+  content: " ";
+  margin-right: .45em;
+}
+
+.picker__button--today:before {
+  top: -0.05em;
+  width: 0;
+  border-top: 0.66em solid #0059bc;
+  border-left: .66em solid transparent;
+}
+
+.picker__button--clear:before {
+  top: -0.25em;
+  width: .66em;
+  border-top: 3px solid #ee2200;
+}
+
+.picker__button--close:before {
+  content: "\D7";
+  top: -0.1em;
+  vertical-align: top;
+  font-size: 1.1em;
+  margin-right: .35em;
+  color: #777777;
+}
+
+.picker__button--today[disabled],
+.picker__button--today[disabled]:hover {
+  background: #f5f5f5;
+  border-color: #f5f5f5;
+  color: #dddddd;
+  cursor: default;
+}
+
+.picker__button--today[disabled]:before {
+  border-top-color: #aaaaaa;
+}
+
+/* ==========================================================================
+   CUSTOM MATERIALIZE STYLES
+   ========================================================================== */
+.picker__box {
+  border-radius: 2px;
+  overflow: hidden;
+}
+
+.picker__date-display {
+  text-align: center;
+  background-color: #26a69a;
+  color: #fff;
+  padding-bottom: 15px;
+  font-weight: 300;
+}
+
+.picker__nav--prev:hover,
+.picker__nav--next:hover {
+  cursor: pointer;
+  color: #000000;
+  background: #a1ded8;
+}
+
+.picker__weekday-display {
+  background-color: #1f897f;
+  padding: 10px;
+  font-weight: 200;
+  letter-spacing: .5;
+  font-size: 1rem;
+  margin-bottom: 15px;
+}
+
+.picker__month-display {
+  text-transform: uppercase;
+  font-size: 2rem;
+}
+
+.picker__day-display {
+  font-size: 4.5rem;
+  font-weight: 400;
+}
+
+.picker__year-display {
+  font-size: 1.8rem;
+  color: rgba(255, 255, 255, 0.4);
+}
+
+.picker__box {
+  padding: 0;
+}
+
+.picker__calendar-container {
+  padding: 0 1rem;
+}
+
+.picker__calendar-container thead {
+  border: none;
+}
+
+.picker__table {
+  margin-top: 0;
+  margin-bottom: .5em;
+}
+
+.picker__day--infocus {
+  color: #595959;
+  letter-spacing: -.3;
+  padding: .75rem 0;
+  font-weight: 400;
+  border: 1px solid transparent;
+}
+
+.picker__day.picker__day--today {
+  color: #26a69a;
+}
+
+.picker__day.picker__day--today.picker__day--selected {
+  color: #fff;
+}
+
+.picker__weekday {
+  font-size: .9rem;
+}
+
+.picker__day--selected,
+.picker__day--selected:hover,
+.picker--focused .picker__day--selected {
+  border-radius: 50%;
+  -webkit-transform: scale(0.9);
+          transform: scale(0.9);
+  background-color: #26a69a;
+  color: #ffffff;
+}
+
+.picker__day--selected.picker__day--outfocus,
+.picker__day--selected:hover.picker__day--outfocus,
+.picker--focused .picker__day--selected.picker__day--outfocus {
+  background-color: #a1ded8;
+}
+
+.picker__footer {
+  text-align: right;
+  padding: 5px 10px;
+}
+
+.picker__close, .picker__today {
+  font-size: 1.1rem;
+  padding: 0 1rem;
+  color: #26a69a;
+}
+
+.picker__nav--prev:before,
+.picker__nav--next:before {
+  content: " ";
+  border-top: .5em solid transparent;
+  border-bottom: .5em solid transparent;
+  border-right: 0.75em solid #676767;
+  width: 0;
+  height: 0;
+  display: block;
+  margin: 0 auto;
+}
+
+.picker__nav--next:before {
+  border-right: 0;
+  border-left: 0.75em solid #676767;
+}
+
+button.picker__today:focus, button.picker__clear:focus, button.picker__close:focus {
+  background-color: #a1ded8;
+}
+
+/* ==========================================================================
+   $BASE-TIME-PICKER
+   ========================================================================== */
+/**
+ * The list of times.
+ */
+.picker__list {
+  list-style: none;
+  padding: 0.75em 0 4.2em;
+  margin: 0;
+}
+
+/**
+ * The times on the clock.
+ */
+.picker__list-item {
+  border-bottom: 1px solid #dddddd;
+  border-top: 1px solid #dddddd;
+  margin-bottom: -1px;
+  position: relative;
+  background: #ffffff;
+  padding: .75em 1.25em;
+}
+
+@media (min-height: 46.75em) {
+  .picker__list-item {
+    padding: .5em 1em;
+  }
+}
+
+/* Hovered time */
+.picker__list-item:hover {
+  cursor: pointer;
+  color: #000000;
+  background: #b1dcfb;
+  border-color: #0089ec;
+  z-index: 10;
+}
+
+/* Highlighted and hovered/focused time */
+.picker__list-item--highlighted {
+  border-color: #0089ec;
+  z-index: 10;
+}
+
+.picker__list-item--highlighted:hover,
+.picker--focused .picker__list-item--highlighted {
+  cursor: pointer;
+  color: #000000;
+  background: #b1dcfb;
+}
+
+/* Selected and hovered/focused time */
+.picker__list-item--selected,
+.picker__list-item--selected:hover,
+.picker--focused .picker__list-item--selected {
+  background: #0089ec;
+  color: #ffffff;
+  z-index: 10;
+}
+
+/* Disabled time */
+.picker__list-item--disabled,
+.picker__list-item--disabled:hover,
+.picker--focused .picker__list-item--disabled {
+  background: #f5f5f5;
+  border-color: #f5f5f5;
+  color: #dddddd;
+  cursor: default;
+  border-color: #dddddd;
+  z-index: auto;
+}
+
+/**
+ * The clear button
+ */
+.picker--time .picker__button--clear {
+  display: block;
+  width: 80%;
+  margin: 1em auto 0;
+  padding: 1em 1.25em;
+  background: none;
+  border: 0;
+  font-weight: 500;
+  font-size: .67em;
+  text-align: center;
+  text-transform: uppercase;
+  color: #666;
+}
+
+.picker--time .picker__button--clear:hover,
+.picker--time .picker__button--clear:focus {
+  color: #000000;
+  background: #b1dcfb;
+  background: #ee2200;
+  border-color: #ee2200;
+  cursor: pointer;
+  color: #ffffff;
+  outline: none;
+}
+
+.picker--time .picker__button--clear:before {
+  top: -0.25em;
+  color: #666;
+  font-size: 1.25em;
+  font-weight: bold;
+}
+
+.picker--time .picker__button--clear:hover:before,
+.picker--time .picker__button--clear:focus:before {
+  color: #ffffff;
+}
+
+/* ==========================================================================
+   $DEFAULT-TIME-PICKER
+   ========================================================================== */
+/**
+ * The frame the bounds the time picker.
+ */
+.picker--time .picker__frame {
+  min-width: 256px;
+  max-width: 320px;
+}
+
+/**
+ * The picker box.
+ */
+.picker--time .picker__box {
+  font-size: 1em;
+  background: #f2f2f2;
+  padding: 0;
+}
+
+@media (min-height: 40.125em) {
+  .picker--time .picker__box {
+    margin-bottom: 5em;
+  }
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/css/materialize.min.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/css/materialize.min.css
new file mode 100644 (file)
index 0000000..9104745
--- /dev/null
@@ -0,0 +1,16 @@
+/*!
+ * Materialize v0.98.0 (http://materializecss.com)
+ * Copyright 2014-2015 Materialize
+ * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE)
+ */
+.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:transparent !important}.transparent-text{color:transparent !important}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default) li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.valign-wrapper .valign{display:block}.clearfix{clear:both}.z-depth-0{box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-floating,.dropdown-content,.collapsible,.side-nav{box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12),0 3px 1px -2px rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-floating:hover{box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{box-shadow:0 6px 10px 0 rgba(0,0,0,0.14),0 1px 18px 0 rgba(0,0,0,0.12),0 3px 5px -1px rgba(0,0,0,0.3)}.z-depth-4,.modal{box-shadow:0 8px 10px 1px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.3)}.z-depth-5{box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -5px rgba(0,0,0,0.3)}.hoverable{transition:box-shadow .25s;box-shadow:0}.hoverable:hover{transition:box-shadow .25s;box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax img{display:none;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}footer.page-footer{padding-top:20px;background-color:#ee6e73}footer.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table}table.bordered>thead>tr,table.bordered>tbody>tr{border-bottom:1px solid #d0d0d0}table.striped>tbody>tr:nth-child(odd){background-color:#f2f2f2}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:#f2f2f2}table.centered thead tr th,table.centered tbody tr td{text-align:center}thead{border-bottom:1px solid #d0d0d0}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid #d0d0d0}table.responsive-table.bordered th{border-bottom:0;border-left:0}table.responsive-table.bordered td{border-left:0;border-right:0;border-bottom:0}table.responsive-table.bordered tr{border:0}table.responsive-table.bordered tbody tr{border-right:1px solid #d0d0d0}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar .circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-top:calc(1.5rem - 11px)}.side-nav span.badge{margin-top:calc(24px - 11px)}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.container .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.button-collapse{display:none}}nav .button-collapse{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .button-collapse i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0;white-space:nowrap}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.button-collapse,nav a.button-collapse i{height:64px;line-height:64px}.navbar-fixed{height:64px}}@font-face{font-family:"Roboto";src:local(Roboto Thin),url("../fonts/roboto/Roboto-Thin.eot");src:url("../fonts/roboto/Roboto-Thin.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Thin.woff2") format("woff2"),url("../fonts/roboto/Roboto-Thin.woff") format("woff"),url("../fonts/roboto/Roboto-Thin.ttf") format("truetype");font-weight:200}@font-face{font-family:"Roboto";src:local(Roboto Light),url("../fonts/roboto/Roboto-Light.eot");src:url("../fonts/roboto/Roboto-Light.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Light.woff2") format("woff2"),url("../fonts/roboto/Roboto-Light.woff") format("woff"),url("../fonts/roboto/Roboto-Light.ttf") format("truetype");font-weight:300}@font-face{font-family:"Roboto";src:local(Roboto Regular),url("../fonts/roboto/Roboto-Regular.eot");src:url("../fonts/roboto/Roboto-Regular.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Regular.woff2") format("woff2"),url("../fonts/roboto/Roboto-Regular.woff") format("woff"),url("../fonts/roboto/Roboto-Regular.ttf") format("truetype");font-weight:400}@font-face{font-family:"Roboto";src:url("../fonts/roboto/Roboto-Medium.eot");src:url("../fonts/roboto/Roboto-Medium.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Medium.woff2") format("woff2"),url("../fonts/roboto/Roboto-Medium.woff") format("woff"),url("../fonts/roboto/Roboto-Medium.ttf") format("truetype");font-weight:500}@font-face{font-family:"Roboto";src:url("../fonts/roboto/Roboto-Bold.eot");src:url("../fonts/roboto/Roboto-Bold.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Bold.woff2") format("woff2"),url("../fonts/roboto/Roboto-Bold.woff") format("woff"),url("../fonts/roboto/Roboto-Bold.ttf") format("truetype");font-weight:700}a{text-decoration:none}html{line-height:1.5;font-family:"Roboto", sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.1}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.1rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:1.78rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.46rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.14rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:.82rem 0 .656rem 0}h6{font-size:1rem;line-height:110%;margin:.5rem 0 .4rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light,footer.page-footer .footer-copyright{font-weight:300}.thin{font-weight:200}.flow-text{font-weight:300}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{transition:box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;transition:box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0;color:inherit}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{position:relative;background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);padding:16px 24px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;clear:both;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;word-break:break-all;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.toast .btn,.toast .btn-large,.toast .btn-flat{margin:0;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}@media only screen and (min-width: 601px) and (max-width: 992px){.toast{float:left}}@media only screen and (min-width: 993px){.toast{float:right}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;transition:color .28s ease}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.7);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 2rem;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.btn-floating.disabled,.btn-large.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-flat:disabled,.btn[disabled],[disabled].btn-large,.btn-floating[disabled],.btn-large[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,[disabled].btn-large:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-floating,.btn-large,.btn-flat{outline:0}.btn i,.btn-large i,.btn-floating i,.btn-large i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;transition:.2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;transition:.3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px}.btn-floating.btn-large i{line-height:56px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:0;-webkit-transform:translateY(50%);transform:translateY(50%)}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:998}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.horizontal{padding:0 0 0 15px}.fixed-action-btn.horizontal ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.horizontal ul li{display:inline-block;margin:15px 15px 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0}.fixed-action-btn.toolbar ul li{-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;transition:background-color .2s}.btn-flat:focus,.btn-flat:active{background-color:transparent}.btn-flat:focus,.btn-flat:hover{background-color:rgba(0,0,0,0.1);box-shadow:none}.btn-flat:active{background-color:rgba(0,0,0,0.2)}.btn-flat.disabled{background-color:transparent !important;color:#b3b3b3 !important;cursor:default}.btn-large{height:54px;line-height:54px}.btn-large i{font-size:1.6rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;max-height:650px;overflow-y:auto;opacity:0;position:absolute;z-index:999;will-change:width, height}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left;text-transform:none}.dropdown-content li:hover,.dropdown-content li.active,.dropdown-content li.selected{background-color:#eee}.dropdown-content li.active.selected{background-color:#e1e1e1}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px}/*!
+ * Waves v0.6.0
+ * http://fian.my.id/Waves
+ *
+ * Copyright 2014 Alfiana E. Sibuea and other contributors
+ * Released under the MIT license
+ * https://github.com/fians/Waves/blob/master/LICENSE
+ */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, #fff 100%, #000 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-flat{float:right;margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-100px;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:block;cursor:pointer;min-height:3rem;line-height:3rem;padding:0 1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header i{width:2rem;font-size:1.6rem;line-height:3rem;display:block;float:left;text-align:center;margin-right:1rem}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box;padding:2rem}.side-nav .collapsible,.side-nav.fixed .collapsible{border:none;box-shadow:none}.side-nav .collapsible li,.side-nav.fixed .collapsible li{padding:0}.side-nav .collapsible-header,.side-nav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.side-nav .collapsible-header:hover,.side-nav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.side-nav .collapsible-header i,.side-nav.fixed .collapsible-header i{line-height:inherit}.side-nav .collapsible-body,.side-nav.fixed .collapsible-body{border:0;background-color:#fff}.side-nav .collapsible-body li a,.side-nav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;box-shadow:none;margin:0 0 20px 0;min-height:45px;outline:none;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .chip.selected{background-color:#26a69a;color:#fff}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:1rem;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;box-shadow:none !important}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}:-moz-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}input:not([type]),input[type=text],input[type=password],input[type=email],input[type=url],input[type=time],input[type=date],input[type=datetime],input[type=datetime-local],input[type=tel],input[type=number],input[type=search],textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:1rem;margin:0 0 20px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:all 0.3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:disabled,input[type=text][readonly="readonly"],input[type=password]:disabled,input[type=password][readonly="readonly"],input[type=email]:disabled,input[type=email][readonly="readonly"],input[type=url]:disabled,input[type=url][readonly="readonly"],input[type=time]:disabled,input[type=time][readonly="readonly"],input[type=date]:disabled,input[type=date][readonly="readonly"],input[type=datetime]:disabled,input[type=datetime][readonly="readonly"],input[type=datetime-local]:disabled,input[type=datetime-local][readonly="readonly"],input[type=tel]:disabled,input[type=tel][readonly="readonly"],input[type=number]:disabled,input[type=number][readonly="readonly"],input[type=search]:disabled,input[type=search][readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.26);border-bottom:1px dotted rgba(0,0,0,0.26)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:disabled+label,input[type=text][readonly="readonly"]+label,input[type=password]:disabled+label,input[type=password][readonly="readonly"]+label,input[type=email]:disabled+label,input[type=email][readonly="readonly"]+label,input[type=url]:disabled+label,input[type=url][readonly="readonly"]+label,input[type=time]:disabled+label,input[type=time][readonly="readonly"]+label,input[type=date]:disabled+label,input[type=date][readonly="readonly"]+label,input[type=datetime]:disabled+label,input[type=datetime][readonly="readonly"]+label,input[type=datetime-local]:disabled+label,input[type=datetime-local][readonly="readonly"]+label,input[type=tel]:disabled+label,input[type=tel][readonly="readonly"]+label,input[type=number]:disabled+label,input[type=number][readonly="readonly"]+label,input[type=search]:disabled+label,input[type=search][readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.26)}input:not([type]):focus:not([readonly]),input[type=text]:focus:not([readonly]),input[type=password]:focus:not([readonly]),input[type=email]:focus:not([readonly]),input[type=url]:focus:not([readonly]),input[type=time]:focus:not([readonly]),input[type=date]:focus:not([readonly]),input[type=datetime]:focus:not([readonly]),input[type=datetime-local]:focus:not([readonly]),input[type=tel]:focus:not([readonly]),input[type=number]:focus:not([readonly]),input[type=search]:focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:focus:not([readonly])+label,input[type=password]:focus:not([readonly])+label,input[type=email]:focus:not([readonly])+label,input[type=url]:focus:not([readonly])+label,input[type=time]:focus:not([readonly])+label,input[type=date]:focus:not([readonly])+label,input[type=datetime]:focus:not([readonly])+label,input[type=datetime-local]:focus:not([readonly])+label,input[type=tel]:focus:not([readonly])+label,input[type=number]:focus:not([readonly])+label,input[type=search]:focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]).valid,input:not([type]):focus.valid,input[type=text].valid,input[type=text]:focus.valid,input[type=password].valid,input[type=password]:focus.valid,input[type=email].valid,input[type=email]:focus.valid,input[type=url].valid,input[type=url]:focus.valid,input[type=time].valid,input[type=time]:focus.valid,input[type=date].valid,input[type=date]:focus.valid,input[type=datetime].valid,input[type=datetime]:focus.valid,input[type=datetime-local].valid,input[type=datetime-local]:focus.valid,input[type=tel].valid,input[type=tel]:focus.valid,input[type=number].valid,input[type=number]:focus.valid,input[type=search].valid,input[type=search]:focus.valid,textarea.materialize-textarea.valid,textarea.materialize-textarea:focus.valid{border-bottom:1px solid #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input:not([type]).valid+label:after,input:not([type]):focus.valid+label:after,input[type=text].valid+label:after,input[type=text]:focus.valid+label:after,input[type=password].valid+label:after,input[type=password]:focus.valid+label:after,input[type=email].valid+label:after,input[type=email]:focus.valid+label:after,input[type=url].valid+label:after,input[type=url]:focus.valid+label:after,input[type=time].valid+label:after,input[type=time]:focus.valid+label:after,input[type=date].valid+label:after,input[type=date]:focus.valid+label:after,input[type=datetime].valid+label:after,input[type=datetime]:focus.valid+label:after,input[type=datetime-local].valid+label:after,input[type=datetime-local]:focus.valid+label:after,input[type=tel].valid+label:after,input[type=tel]:focus.valid+label:after,input[type=number].valid+label:after,input[type=number]:focus.valid+label:after,input[type=search].valid+label:after,input[type=search]:focus.valid+label:after,textarea.materialize-textarea.valid+label:after,textarea.materialize-textarea:focus.valid+label:after{content:attr(data-success);color:#4CAF50;opacity:1}input:not([type]).invalid,input:not([type]):focus.invalid,input[type=text].invalid,input[type=text]:focus.invalid,input[type=password].invalid,input[type=password]:focus.invalid,input[type=email].invalid,input[type=email]:focus.invalid,input[type=url].invalid,input[type=url]:focus.invalid,input[type=time].invalid,input[type=time]:focus.invalid,input[type=date].invalid,input[type=date]:focus.invalid,input[type=datetime].invalid,input[type=datetime]:focus.invalid,input[type=datetime-local].invalid,input[type=datetime-local]:focus.invalid,input[type=tel].invalid,input[type=tel]:focus.invalid,input[type=number].invalid,input[type=number]:focus.invalid,input[type=search].invalid,input[type=search]:focus.invalid,textarea.materialize-textarea.invalid,textarea.materialize-textarea:focus.invalid{border-bottom:1px solid #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).invalid+label:after,input:not([type]):focus.invalid+label:after,input[type=text].invalid+label:after,input[type=text]:focus.invalid+label:after,input[type=password].invalid+label:after,input[type=password]:focus.invalid+label:after,input[type=email].invalid+label:after,input[type=email]:focus.invalid+label:after,input[type=url].invalid+label:after,input[type=url]:focus.invalid+label:after,input[type=time].invalid+label:after,input[type=time]:focus.invalid+label:after,input[type=date].invalid+label:after,input[type=date]:focus.invalid+label:after,input[type=datetime].invalid+label:after,input[type=datetime]:focus.invalid+label:after,input[type=datetime-local].invalid+label:after,input[type=datetime-local]:focus.invalid+label:after,input[type=tel].invalid+label:after,input[type=tel]:focus.invalid+label:after,input[type=number].invalid+label:after,input[type=number]:focus.invalid+label:after,input[type=search].invalid+label:after,input[type=search]:focus.invalid+label:after,textarea.materialize-textarea.invalid+label:after,textarea.materialize-textarea:focus.invalid+label:after{content:attr(data-error);color:#F44336;opacity:1}input:not([type]).validate+label,input[type=text].validate+label,input[type=password].validate+label,input[type=email].validate+label,input[type=url].validate+label,input[type=time].validate+label,input[type=date].validate+label,input[type=datetime].validate+label,input[type=datetime-local].validate+label,input[type=tel].validate+label,input[type=number].validate+label,input[type=search].validate+label,textarea.materialize-textarea.validate+label{width:100%;pointer-events:none}input:not([type])+label:after,input[type=text]+label:after,input[type=password]+label:after,input[type=email]+label:after,input[type=url]+label:after,input[type=time]+label:after,input[type=date]+label:after,input[type=datetime]+label:after,input[type=datetime-local]+label:after,input[type=tel]+label:after,input[type=number]+label:after,input[type=search]+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:60px;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field label{color:#9e9e9e;position:absolute;top:0.8rem;left:0;font-size:1rem;cursor:text;transition:.2s ease-out}.input-field label:not(.label-icon).active{font-size:.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;padding-left:4rem;width:calc(100% - 4rem)}.input-field input[type=search]:focus{background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus+label i,.input-field input[type=search]:focus ~ .mdi-navigation-close,.input-field input[type=search]:focus ~ .material-icons{color:#444}.input-field input[type=search]+label{left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{overflow-y:hidden;padding:.8rem 0 1.6rem 0;resize:none;min-height:3rem}.hiddendiv{display:none;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem}.autocomplete-content{margin-top:-15px;display:block;opacity:1;position:static}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;left:-9999px;opacity:0}[type="radio"]:not(:checked)+label,[type="radio"]:checked+label{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+label:before,[type="radio"]+label:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+label:before,[type="radio"]:not(:checked)+label:after,[type="radio"]:checked+label:before,[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:before,[type="radio"].with-gap:checked+label:after{border-radius:50%}[type="radio"]:not(:checked)+label:before,[type="radio"]:not(:checked)+label:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+label:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+label:before{border:2px solid transparent}[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:before,[type="radio"].with-gap:checked+label:after{border:2px solid #26a69a}[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:after{background-color:#26a69a}[type="radio"]:checked+label:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+label:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+label:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+label:before{border:2px solid rgba(0,0,0,0.26)}[type="radio"].with-gap:disabled:checked+label:after{border:none;background-color:rgba(0,0,0,0.26)}[type="radio"]:disabled:not(:checked)+label:before,[type="radio"]:disabled:checked+label:before{background-color:transparent;border-color:rgba(0,0,0,0.26)}[type="radio"]:disabled+label{color:rgba(0,0,0,0.26)}[type="radio"]:disabled:not(:checked)+label:before{border-color:rgba(0,0,0,0.26)}[type="radio"]:disabled:checked+label:after{background-color:rgba(0,0,0,0.26);border-color:#BDBDBD}form p{margin-bottom:10px;text-align:left}form p:last-child{margin-bottom:0}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;left:-9999px;opacity:0}[type="checkbox"]+label{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}[type="checkbox"]+label:before,[type="checkbox"]:not(.filled-in)+label:after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:2px;transition:.2s}[type="checkbox"]:not(.filled-in)+label:after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+label:before{border:none;background-color:rgba(0,0,0,0.26)}[type="checkbox"].tabbed:focus+label:after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+label:before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+label:before{border-right:2px solid rgba(0,0,0,0.26);border-bottom:2px solid rgba(0,0,0,0.26)}[type="checkbox"]:indeterminate+label:before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+label:before{border-right:2px solid rgba(0,0,0,0.26);background-color:transparent}[type="checkbox"].filled-in+label:after{border-radius:2px}[type="checkbox"].filled-in+label:before,[type="checkbox"].filled-in+label:after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+label:before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:20% 40%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+label:after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+label:before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+label:after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+label:after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+label:after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+label:before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+label:after{border-color:transparent;background-color:#BDBDBD}[type="checkbox"].filled-in:disabled:checked+label:before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+label:after{background-color:#BDBDBD;border-color:#BDBDBD}.switch,.switch *{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a;left:24px}.switch label .lever{content:"";display:inline-block;position:relative;width:40px;height:15px;background-color:#818181;border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:after{content:"";position:absolute;display:inline-block;width:21px;height:21px;background-color:#F1F1F1;border-radius:21px;box-shadow:0 1px 3px 1px rgba(0,0,0,0.4);left:-5px;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after{box-shadow:0 1px 3px 1px rgba(0,0,0,0.4),0 0 0 15px rgba(38,166,154,0.1)}input[type=checkbox]:not(:disabled) ~ .lever:active:after,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after{box-shadow:0 1px 3px 1px rgba(0,0,0,0.4),0 0 0 15px rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#BDBDBD}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:1rem;margin:0 0 20px 0;padding:0;display:block}.select-wrapper span.caret{color:initial;position:absolute;right:0;top:0;bottom:0;height:10px;margin:auto 0;font-size:10px;line-height:10px}.select-wrapper span.caret.disabled{color:rgba(0,0,0,0.26)}.select-wrapper+label{position:absolute;top:-14px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.3)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.3);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;border-bottom:1px solid rgba(0,0,0,0.3)}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;top:10px;margin-left:-6px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;border:none;height:14px;width:14px;border-radius:50%;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0;transition:.3s}input[type=range]:focus::-webkit-slider-runnable-track{background:#ccc}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#ddd;border:none}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input[type=range]:focus::-moz-range-track{background:#ccc}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a}input[type=range]:focus::-ms-fill-lower{background:#888}input[type=range]:focus::-ms-fill-upper{background:#ccc}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:20px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:19px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:18px;border-left:2px solid #ee6e73}.side-nav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.side-nav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.side-nav .collapsible{margin:0}.side-nav li{float:none;line-height:48px}.side-nav li.active{background-color:rgba(0,0,0,0.05)}.side-nav a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.side-nav a:hover{background-color:rgba(0,0,0,0.05)}.side-nav a.btn,.side-nav a.btn-large,.side-nav a.btn-large,.side-nav a.btn-flat,.side-nav a.btn-floating{margin:10px 15px}.side-nav a.btn,.side-nav a.btn-large,.side-nav a.btn-large,.side-nav a.btn-floating{color:#fff}.side-nav a.btn-flat{color:#343434}.side-nav a.btn:hover,.side-nav a.btn-large:hover,.side-nav a.btn-large:hover{background-color:#2bbbad}.side-nav a.btn-floating:hover{background-color:#26a69a}.side-nav li>a>i,.side-nav li>a>[class^="mdi-"],.side-nav li>a>[class*="mdi-"],.side-nav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.side-nav .divider{margin:8px 0 0 0}.side-nav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.side-nav .subheader:hover{background-color:transparent}.side-nav .userView{position:relative;padding:32px 32px 0;margin-bottom:8px}.side-nav .userView>a{height:auto;padding:0}.side-nav .userView>a:hover{background-color:transparent}.side-nav .userView .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.side-nav .userView .circle,.side-nav .userView .name,.side-nav .userView .email{display:block}.side-nav .userView .circle{height:64px;width:64px}.side-nav .userView .name,.side-nav .userView .email{font-size:14px;line-height:24px}.side-nav .userView .name{margin-top:16px;font-weight:500}.side-nav .userView .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.side-nav.fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.side-nav.fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.side-nav.fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.side-nav.fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.side-nav a{padding:0 16px}.side-nav .userView{padding:16px 16px 0}}.side-nav .collapsible-body>ul:not(.collapsible)>li.active,.side-nav.fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.side-nav .collapsible-body>ul:not(.collapsible)>li.active a,.side-nav.fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}#sidenav-overlay{position:fixed;top:0;left:0;right:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;will-change:opacity}.preloader-wrapper{display:inline-block;position:relative;width:48px;height:48px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0;height:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{display:none;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.picker{font-size:16px;text-align:left;line-height:1.2;color:#000000;position:absolute;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}/*!
+ * Default mobile-first, responsive styling for pickadate.js
+ * Demo: http://amsul.github.io/pickadate.js
+ */.picker__holder,.picker__frame{bottom:0;left:0;right:0;top:100%}.picker__holder{position:fixed;transition:background 0.15s ease-out, top 0s 0.15s;-webkit-backface-visibility:hidden}.picker__frame{position:absolute;margin:0 auto;min-width:256px;width:300px;max-height:350px;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;opacity:0;transition:all 0.15s ease-out}@media (min-height: 28.875em){.picker__frame{overflow:visible;top:auto;bottom:-100%;max-height:80%}}@media (min-height: 40.125em){.picker__frame{margin-bottom:7.5%}}.picker__wrap{display:table;width:100%;height:100%}@media (min-height: 28.875em){.picker__wrap{display:block}}.picker__box{background:#ffffff;display:table-cell;vertical-align:middle}@media (min-height: 28.875em){.picker__box{display:block;border:1px solid #777777;border-top-color:#898989;border-bottom-width:0;border-radius:5px 5px 0 0;box-shadow:0 12px 36px 16px rgba(0,0,0,0.24)}}.picker--opened .picker__holder{top:0;background:transparent;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E000000,endColorstr=#1E000000)";zoom:1;background:rgba(0,0,0,0.32);transition:background 0.15s ease-out}.picker--opened .picker__frame{top:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);-moz-opacity:1;opacity:1}@media (min-height: 35.875em){.picker--opened .picker__frame{top:10%;bottom:auto}}.picker__input.picker__input--active{border-color:#E3F2FD}.picker__frame{margin:0 auto;max-width:325px}@media (min-height: 38.875em){.picker--opened .picker__frame{top:10%;bottom:auto}}.picker__box{padding:0 1em}.picker__header{text-align:center;position:relative;margin-top:.75em}.picker__month,.picker__year{display:inline-block;margin-left:.25em;margin-right:.25em}.picker__select--month,.picker__select--year{height:2em;padding:0;margin-left:.25em;margin-right:.25em}.picker__select--month.browser-default{display:inline;background-color:#FFFFFF;width:40%}.picker__select--year.browser-default{display:inline;background-color:#FFFFFF;width:26%}.picker__select--month:focus,.picker__select--year:focus{border-color:rgba(0,0,0,0.05)}.picker__nav--prev,.picker__nav--next{position:absolute;padding:.5em 1.25em;width:1em;height:1em;box-sizing:content-box;top:-0.25em}.picker__nav--prev{left:-1em;padding-right:1.25em}.picker__nav--next{right:-1em;padding-left:1.25em}.picker__nav--disabled,.picker__nav--disabled:hover,.picker__nav--disabled:before,.picker__nav--disabled:before:hover{cursor:default;background:none;border-right-color:#f5f5f5;border-left-color:#f5f5f5}.picker__table{text-align:center;border-collapse:collapse;border-spacing:0;table-layout:fixed;font-size:1rem;width:100%;margin-top:.75em;margin-bottom:.5em}.picker__table th,.picker__table td{text-align:center}.picker__table td{margin:0;padding:0}.picker__weekday{width:14.285714286%;font-size:.75em;padding-bottom:.25em;color:#999999;font-weight:500}@media (min-height: 33.875em){.picker__weekday{padding-bottom:.5em}}.picker__day--today{position:relative;color:#595959;letter-spacing:-.3;padding:.75rem 0;font-weight:400;border:1px solid transparent}.picker__day--disabled:before{border-top-color:#aaaaaa}.picker__day--infocus:hover{cursor:pointer;color:#000;font-weight:500}.picker__day--outfocus{display:none;padding:.75rem 0;color:#fff}.picker__day--outfocus:hover{cursor:pointer;color:#dddddd;font-weight:500}.picker__day--highlighted:hover,.picker--focused .picker__day--highlighted{cursor:pointer}.picker__day--selected,.picker__day--selected:hover,.picker--focused .picker__day--selected{border-radius:50%;-webkit-transform:scale(0.75);transform:scale(0.75);background:#0089ec;color:#ffffff}.picker__day--disabled,.picker__day--disabled:hover,.picker--focused .picker__day--disabled{background:#f5f5f5;border-color:#f5f5f5;color:#dddddd;cursor:default}.picker__day--highlighted.picker__day--disabled,.picker__day--highlighted.picker__day--disabled:hover{background:#bbbbbb}.picker__footer{text-align:center;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.picker__button--today,.picker__button--clear,.picker__button--close{border:1px solid #ffffff;background:#ffffff;font-size:.8em;padding:.66em 0;font-weight:bold;width:33%;display:inline-block;vertical-align:bottom}.picker__button--today:hover,.picker__button--clear:hover,.picker__button--close:hover{cursor:pointer;color:#000000;background:#b1dcfb;border-bottom-color:#b1dcfb}.picker__button--today:focus,.picker__button--clear:focus,.picker__button--close:focus{background:#b1dcfb;border-color:rgba(0,0,0,0.05);outline:none}.picker__button--today:before,.picker__button--clear:before,.picker__button--close:before{position:relative;display:inline-block;height:0}.picker__button--today:before,.picker__button--clear:before{content:" ";margin-right:.45em}.picker__button--today:before{top:-0.05em;width:0;border-top:0.66em solid #0059bc;border-left:.66em solid transparent}.picker__button--clear:before{top:-0.25em;width:.66em;border-top:3px solid #ee2200}.picker__button--close:before{content:"\D7";top:-0.1em;vertical-align:top;font-size:1.1em;margin-right:.35em;color:#777777}.picker__button--today[disabled],.picker__button--today[disabled]:hover{background:#f5f5f5;border-color:#f5f5f5;color:#dddddd;cursor:default}.picker__button--today[disabled]:before{border-top-color:#aaaaaa}.picker__box{border-radius:2px;overflow:hidden}.picker__date-display{text-align:center;background-color:#26a69a;color:#fff;padding-bottom:15px;font-weight:300}.picker__nav--prev:hover,.picker__nav--next:hover{cursor:pointer;color:#000000;background:#a1ded8}.picker__weekday-display{background-color:#1f897f;padding:10px;font-weight:200;letter-spacing:.5;font-size:1rem;margin-bottom:15px}.picker__month-display{text-transform:uppercase;font-size:2rem}.picker__day-display{font-size:4.5rem;font-weight:400}.picker__year-display{font-size:1.8rem;color:rgba(255,255,255,0.4)}.picker__box{padding:0}.picker__calendar-container{padding:0 1rem}.picker__calendar-container thead{border:none}.picker__table{margin-top:0;margin-bottom:.5em}.picker__day--infocus{color:#595959;letter-spacing:-.3;padding:.75rem 0;font-weight:400;border:1px solid transparent}.picker__day.picker__day--today{color:#26a69a}.picker__day.picker__day--today.picker__day--selected{color:#fff}.picker__weekday{font-size:.9rem}.picker__day--selected,.picker__day--selected:hover,.picker--focused .picker__day--selected{border-radius:50%;-webkit-transform:scale(0.9);transform:scale(0.9);background-color:#26a69a;color:#ffffff}.picker__day--selected.picker__day--outfocus,.picker__day--selected:hover.picker__day--outfocus,.picker--focused .picker__day--selected.picker__day--outfocus{background-color:#a1ded8}.picker__footer{text-align:right;padding:5px 10px}.picker__close,.picker__today{font-size:1.1rem;padding:0 1rem;color:#26a69a}.picker__nav--prev:before,.picker__nav--next:before{content:" ";border-top:.5em solid transparent;border-bottom:.5em solid transparent;border-right:0.75em solid #676767;width:0;height:0;display:block;margin:0 auto}.picker__nav--next:before{border-right:0;border-left:0.75em solid #676767}button.picker__today:focus,button.picker__clear:focus,button.picker__close:focus{background-color:#a1ded8}.picker__list{list-style:none;padding:0.75em 0 4.2em;margin:0}.picker__list-item{border-bottom:1px solid #dddddd;border-top:1px solid #dddddd;margin-bottom:-1px;position:relative;background:#ffffff;padding:.75em 1.25em}@media (min-height: 46.75em){.picker__list-item{padding:.5em 1em}}.picker__list-item:hover{cursor:pointer;color:#000000;background:#b1dcfb;border-color:#0089ec;z-index:10}.picker__list-item--highlighted{border-color:#0089ec;z-index:10}.picker__list-item--highlighted:hover,.picker--focused .picker__list-item--highlighted{cursor:pointer;color:#000000;background:#b1dcfb}.picker__list-item--selected,.picker__list-item--selected:hover,.picker--focused .picker__list-item--selected{background:#0089ec;color:#ffffff;z-index:10}.picker__list-item--disabled,.picker__list-item--disabled:hover,.picker--focused .picker__list-item--disabled{background:#f5f5f5;border-color:#f5f5f5;color:#dddddd;cursor:default;border-color:#dddddd;z-index:auto}.picker--time .picker__button--clear{display:block;width:80%;margin:1em auto 0;padding:1em 1.25em;background:none;border:0;font-weight:500;font-size:.67em;text-align:center;text-transform:uppercase;color:#666}.picker--time .picker__button--clear:hover,.picker--time .picker__button--clear:focus{color:#000000;background:#b1dcfb;background:#ee2200;border-color:#ee2200;cursor:pointer;color:#ffffff;outline:none}.picker--time .picker__button--clear:before{top:-0.25em;color:#666;font-size:1.25em;font-weight:bold}.picker--time .picker__button--clear:hover:before,.picker--time .picker__button--clear:focus:before{color:#ffffff}.picker--time .picker__frame{min-width:256px;max-width:320px}.picker--time .picker__box{font-size:1em;background:#f2f2f2;padding:0}@media (min-height: 40.125em){.picker--time .picker__box{margin-bottom:5em}}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/.DS_Store b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/.DS_Store
new file mode 100644 (file)
index 0000000..ae0d28e
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/.DS_Store differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.eot b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.eot
new file mode 100644 (file)
index 0000000..b73776e
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.eot differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.ttf b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.ttf
new file mode 100644 (file)
index 0000000..68822ca
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.ttf differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff
new file mode 100644 (file)
index 0000000..1f75afd
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff2 b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff2
new file mode 100644 (file)
index 0000000..350d1c3
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Bold.woff2 differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.eot b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.eot
new file mode 100644 (file)
index 0000000..072cdc4
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.eot differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.ttf b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.ttf
new file mode 100644 (file)
index 0000000..aa45340
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.ttf differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff
new file mode 100644 (file)
index 0000000..3480c6c
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff2 b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff2
new file mode 100644 (file)
index 0000000..9a4d98c
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Light.woff2 differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.eot b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.eot
new file mode 100644 (file)
index 0000000..f9ad995
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.eot differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.ttf b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.ttf
new file mode 100644 (file)
index 0000000..a3c1a1f
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.ttf differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff
new file mode 100644 (file)
index 0000000..1186773
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff2 b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff2
new file mode 100644 (file)
index 0000000..d10a592
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Medium.woff2 differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.eot b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.eot
new file mode 100644 (file)
index 0000000..9b5e8e4
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.eot differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.ttf b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.ttf
new file mode 100644 (file)
index 0000000..0e58508
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.ttf differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff
new file mode 100644 (file)
index 0000000..f823258
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff2 b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff2
new file mode 100644 (file)
index 0000000..b7082ef
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Regular.woff2 differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.eot b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.eot
new file mode 100644 (file)
index 0000000..2284a3b
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.eot differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.ttf b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.ttf
new file mode 100644 (file)
index 0000000..8779333
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.ttf differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff
new file mode 100644 (file)
index 0000000..2a98c1e
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff2 b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff2
new file mode 100644 (file)
index 0000000..a38025a
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/fonts/roboto/Roboto-Thin.woff2 differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/js/materialize.js b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/js/materialize.js
new file mode 100644 (file)
index 0000000..8c994c4
--- /dev/null
@@ -0,0 +1,8031 @@
+/*!
+ * Materialize v0.98.0 (http://materializecss.com)
+ * Copyright 2014-2015 Materialize
+ * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE)
+ */
+// Check for jQuery.
+if (typeof(jQuery) === 'undefined') {
+  var jQuery;
+  // Check if require is a defined function.
+  if (typeof(require) === 'function') {
+    jQuery = $ = require('jquery');
+  // Else use the dollar sign alias.
+  } else {
+    jQuery = $;
+  }
+}
+;/*
+ * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
+ *
+ * Uses the built in easing capabilities added In jQuery 1.1
+ * to offer multiple easing options
+ *
+ * TERMS OF USE - jQuery Easing
+ *
+ * Open source under the BSD License.
+ *
+ * Copyright © 2008 George McGinley Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the author nor the names of contributors may be used to endorse
+ * or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+*/
+
+// t: current time, b: begInnIng value, c: change In value, d: duration
+jQuery.easing['jswing'] = jQuery.easing['swing'];
+
+jQuery.extend( jQuery.easing,
+{
+       def: 'easeOutQuad',
+       swing: function (x, t, b, c, d) {
+               //alert(jQuery.easing.default);
+               return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
+       },
+       easeInQuad: function (x, t, b, c, d) {
+               return c*(t/=d)*t + b;
+       },
+       easeOutQuad: function (x, t, b, c, d) {
+               return -c *(t/=d)*(t-2) + b;
+       },
+       easeInOutQuad: function (x, t, b, c, d) {
+               if ((t/=d/2) < 1) return c/2*t*t + b;
+               return -c/2 * ((--t)*(t-2) - 1) + b;
+       },
+       easeInCubic: function (x, t, b, c, d) {
+               return c*(t/=d)*t*t + b;
+       },
+       easeOutCubic: function (x, t, b, c, d) {
+               return c*((t=t/d-1)*t*t + 1) + b;
+       },
+       easeInOutCubic: function (x, t, b, c, d) {
+               if ((t/=d/2) < 1) return c/2*t*t*t + b;
+               return c/2*((t-=2)*t*t + 2) + b;
+       },
+       easeInQuart: function (x, t, b, c, d) {
+               return c*(t/=d)*t*t*t + b;
+       },
+       easeOutQuart: function (x, t, b, c, d) {
+               return -c * ((t=t/d-1)*t*t*t - 1) + b;
+       },
+       easeInOutQuart: function (x, t, b, c, d) {
+               if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
+               return -c/2 * ((t-=2)*t*t*t - 2) + b;
+       },
+       easeInQuint: function (x, t, b, c, d) {
+               return c*(t/=d)*t*t*t*t + b;
+       },
+       easeOutQuint: function (x, t, b, c, d) {
+               return c*((t=t/d-1)*t*t*t*t + 1) + b;
+       },
+       easeInOutQuint: function (x, t, b, c, d) {
+               if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
+               return c/2*((t-=2)*t*t*t*t + 2) + b;
+       },
+       easeInSine: function (x, t, b, c, d) {
+               return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
+       },
+       easeOutSine: function (x, t, b, c, d) {
+               return c * Math.sin(t/d * (Math.PI/2)) + b;
+       },
+       easeInOutSine: function (x, t, b, c, d) {
+               return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
+       },
+       easeInExpo: function (x, t, b, c, d) {
+               return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+       },
+       easeOutExpo: function (x, t, b, c, d) {
+               return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+       },
+       easeInOutExpo: function (x, t, b, c, d) {
+               if (t==0) return b;
+               if (t==d) return b+c;
+               if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+               return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+       },
+       easeInCirc: function (x, t, b, c, d) {
+               return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
+       },
+       easeOutCirc: function (x, t, b, c, d) {
+               return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
+       },
+       easeInOutCirc: function (x, t, b, c, d) {
+               if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
+               return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
+       },
+       easeInElastic: function (x, t, b, c, d) {
+               var s=1.70158;var p=0;var a=c;
+               if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
+               if (a < Math.abs(c)) { a=c; var s=p/4; }
+               else var s = p/(2*Math.PI) * Math.asin (c/a);
+               return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+       },
+       easeOutElastic: function (x, t, b, c, d) {
+               var s=1.70158;var p=0;var a=c;
+               if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
+               if (a < Math.abs(c)) { a=c; var s=p/4; }
+               else var s = p/(2*Math.PI) * Math.asin (c/a);
+               return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
+       },
+       easeInOutElastic: function (x, t, b, c, d) {
+               var s=1.70158;var p=0;var a=c;
+               if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
+               if (a < Math.abs(c)) { a=c; var s=p/4; }
+               else var s = p/(2*Math.PI) * Math.asin (c/a);
+               if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+               return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
+       },
+       easeInBack: function (x, t, b, c, d, s) {
+               if (s == undefined) s = 1.70158;
+               return c*(t/=d)*t*((s+1)*t - s) + b;
+       },
+       easeOutBack: function (x, t, b, c, d, s) {
+               if (s == undefined) s = 1.70158;
+               return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
+       },
+       easeInOutBack: function (x, t, b, c, d, s) {
+               if (s == undefined) s = 1.70158;
+               if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
+               return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
+       },
+       easeInBounce: function (x, t, b, c, d) {
+               return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
+       },
+       easeOutBounce: function (x, t, b, c, d) {
+               if ((t/=d) < (1/2.75)) {
+                       return c*(7.5625*t*t) + b;
+               } else if (t < (2/2.75)) {
+                       return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
+               } else if (t < (2.5/2.75)) {
+                       return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
+               } else {
+                       return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
+               }
+       },
+       easeInOutBounce: function (x, t, b, c, d) {
+               if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
+               return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
+       }
+});
+
+/*
+ *
+ * TERMS OF USE - EASING EQUATIONS
+ *
+ * Open source under the BSD License.
+ *
+ * Copyright © 2001 Robert Penner
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the author nor the names of contributors may be used to endorse
+ * or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */;// Custom Easing
+jQuery.extend( jQuery.easing,
+{
+  easeInOutMaterial: function (x, t, b, c, d) {
+    if ((t/=d/2) < 1) return c/2*t*t + b;
+    return c/4*((t-=2)*t*t + 2) + b;
+  }
+});;/*! VelocityJS.org (1.2.3). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */
+/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */
+/*! Note that this has been modified by Materialize to confirm that Velocity is not already being imported. */
+jQuery.Velocity?console.log("Velocity is already loaded. You may be needlessly importing Velocity again; note that Materialize includes Velocity."):(!function(e){function t(e){var t=e.length,a=r.type(e);return"function"===a||r.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===a||0===t||"number"==typeof t&&t>0&&t-1 in e}if(!e.jQuery){var r=function(e,t){return new r.fn.init(e,t)};r.isWindow=function(e){return null!=e&&e==e.window},r.type=function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e},r.isArray=Array.isArray||function(e){return"array"===r.type(e)},r.isPlainObject=function(e){var t;if(!e||"object"!==r.type(e)||e.nodeType||r.isWindow(e))return!1;try{if(e.constructor&&!o.call(e,"constructor")&&!o.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(a){return!1}for(t in e);return void 0===t||o.call(e,t)},r.each=function(e,r,a){var n,o=0,i=e.length,s=t(e);if(a){if(s)for(;i>o&&(n=r.apply(e[o],a),n!==!1);o++);else for(o in e)if(n=r.apply(e[o],a),n===!1)break}else if(s)for(;i>o&&(n=r.call(e[o],o,e[o]),n!==!1);o++);else for(o in e)if(n=r.call(e[o],o,e[o]),n===!1)break;return e},r.data=function(e,t,n){if(void 0===n){var o=e[r.expando],i=o&&a[o];if(void 0===t)return i;if(i&&t in i)return i[t]}else if(void 0!==t){var o=e[r.expando]||(e[r.expando]=++r.uuid);return a[o]=a[o]||{},a[o][t]=n,n}},r.removeData=function(e,t){var n=e[r.expando],o=n&&a[n];o&&r.each(t,function(e,t){delete o[t]})},r.extend=function(){var e,t,a,n,o,i,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[l]||{},l++),"object"!=typeof s&&"function"!==r.type(s)&&(s={}),l===u&&(s=this,l--);u>l;l++)if(null!=(o=arguments[l]))for(n in o)e=s[n],a=o[n],s!==a&&(c&&a&&(r.isPlainObject(a)||(t=r.isArray(a)))?(t?(t=!1,i=e&&r.isArray(e)?e:[]):i=e&&r.isPlainObject(e)?e:{},s[n]=r.extend(c,i,a)):void 0!==a&&(s[n]=a));return s},r.queue=function(e,a,n){function o(e,r){var a=r||[];return null!=e&&(t(Object(e))?!function(e,t){for(var r=+t.length,a=0,n=e.length;r>a;)e[n++]=t[a++];if(r!==r)for(;void 0!==t[a];)e[n++]=t[a++];return e.length=n,e}(a,"string"==typeof e?[e]:e):[].push.call(a,e)),a}if(e){a=(a||"fx")+"queue";var i=r.data(e,a);return n?(!i||r.isArray(n)?i=r.data(e,a,o(n)):i.push(n),i):i||[]}},r.dequeue=function(e,t){r.each(e.nodeType?[e]:e,function(e,a){t=t||"fx";var n=r.queue(a,t),o=n.shift();"inprogress"===o&&(o=n.shift()),o&&("fx"===t&&n.unshift("inprogress"),o.call(a,function(){r.dequeue(a,t)}))})},r.fn=r.prototype={init:function(e){if(e.nodeType)return this[0]=e,this;throw new Error("Not a DOM node.")},offset:function(){var t=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:t.top+(e.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:t.left+(e.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){function e(){for(var e=this.offsetParent||document;e&&"html"===!e.nodeType.toLowerCase&&"static"===e.style.position;)e=e.offsetParent;return e||document}var t=this[0],e=e.apply(t),a=this.offset(),n=/^(?:body|html)$/i.test(e.nodeName)?{top:0,left:0}:r(e).offset();return a.top-=parseFloat(t.style.marginTop)||0,a.left-=parseFloat(t.style.marginLeft)||0,e.style&&(n.top+=parseFloat(e.style.borderTopWidth)||0,n.left+=parseFloat(e.style.borderLeftWidth)||0),{top:a.top-n.top,left:a.left-n.left}}};var a={};r.expando="velocity"+(new Date).getTime(),r.uuid=0;for(var n={},o=n.hasOwnProperty,i=n.toString,s="Boolean Number String Function Array Date RegExp Object Error".split(" "),l=0;l<s.length;l++)n["[object "+s[l]+"]"]=s[l].toLowerCase();r.fn.init.prototype=r.fn,e.Velocity={Utilities:r}}}(window),function(e){"object"==typeof module&&"object"==typeof module.exports?module.exports=e():"function"==typeof define&&define.amd?define(e):e()}(function(){return function(e,t,r,a){function n(e){for(var t=-1,r=e?e.length:0,a=[];++t<r;){var n=e[t];n&&a.push(n)}return a}function o(e){return m.isWrapped(e)?e=[].slice.call(e):m.isNode(e)&&(e=[e]),e}function i(e){var t=f.data(e,"velocity");return null===t?a:t}function s(e){return function(t){return Math.round(t*e)*(1/e)}}function l(e,r,a,n){function o(e,t){return 1-3*t+3*e}function i(e,t){return 3*t-6*e}function s(e){return 3*e}function l(e,t,r){return((o(t,r)*e+i(t,r))*e+s(t))*e}function u(e,t,r){return 3*o(t,r)*e*e+2*i(t,r)*e+s(t)}function c(t,r){for(var n=0;m>n;++n){var o=u(r,e,a);if(0===o)return r;var i=l(r,e,a)-t;r-=i/o}return r}function p(){for(var t=0;b>t;++t)w[t]=l(t*x,e,a)}function f(t,r,n){var o,i,s=0;do i=r+(n-r)/2,o=l(i,e,a)-t,o>0?n=i:r=i;while(Math.abs(o)>h&&++s<v);return i}function d(t){for(var r=0,n=1,o=b-1;n!=o&&w[n]<=t;++n)r+=x;--n;var i=(t-w[n])/(w[n+1]-w[n]),s=r+i*x,l=u(s,e,a);return l>=y?c(t,s):0==l?s:f(t,r,r+x)}function g(){V=!0,(e!=r||a!=n)&&p()}var m=4,y=.001,h=1e-7,v=10,b=11,x=1/(b-1),S="Float32Array"in t;if(4!==arguments.length)return!1;for(var P=0;4>P;++P)if("number"!=typeof arguments[P]||isNaN(arguments[P])||!isFinite(arguments[P]))return!1;e=Math.min(e,1),a=Math.min(a,1),e=Math.max(e,0),a=Math.max(a,0);var w=S?new Float32Array(b):new Array(b),V=!1,C=function(t){return V||g(),e===r&&a===n?t:0===t?0:1===t?1:l(d(t),r,n)};C.getControlPoints=function(){return[{x:e,y:r},{x:a,y:n}]};var T="generateBezier("+[e,r,a,n]+")";return C.toString=function(){return T},C}function u(e,t){var r=e;return m.isString(e)?b.Easings[e]||(r=!1):r=m.isArray(e)&&1===e.length?s.apply(null,e):m.isArray(e)&&2===e.length?x.apply(null,e.concat([t])):m.isArray(e)&&4===e.length?l.apply(null,e):!1,r===!1&&(r=b.Easings[b.defaults.easing]?b.defaults.easing:v),r}function c(e){if(e){var t=(new Date).getTime(),r=b.State.calls.length;r>1e4&&(b.State.calls=n(b.State.calls));for(var o=0;r>o;o++)if(b.State.calls[o]){var s=b.State.calls[o],l=s[0],u=s[2],d=s[3],g=!!d,y=null;d||(d=b.State.calls[o][3]=t-16);for(var h=Math.min((t-d)/u.duration,1),v=0,x=l.length;x>v;v++){var P=l[v],V=P.element;if(i(V)){var C=!1;if(u.display!==a&&null!==u.display&&"none"!==u.display){if("flex"===u.display){var T=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];f.each(T,function(e,t){S.setPropertyValue(V,"display",t)})}S.setPropertyValue(V,"display",u.display)}u.visibility!==a&&"hidden"!==u.visibility&&S.setPropertyValue(V,"visibility",u.visibility);for(var k in P)if("element"!==k){var A,F=P[k],j=m.isString(F.easing)?b.Easings[F.easing]:F.easing;if(1===h)A=F.endValue;else{var E=F.endValue-F.startValue;if(A=F.startValue+E*j(h,u,E),!g&&A===F.currentValue)continue}if(F.currentValue=A,"tween"===k)y=A;else{if(S.Hooks.registered[k]){var H=S.Hooks.getRoot(k),N=i(V).rootPropertyValueCache[H];N&&(F.rootPropertyValue=N)}var L=S.setPropertyValue(V,k,F.currentValue+(0===parseFloat(A)?"":F.unitType),F.rootPropertyValue,F.scrollData);S.Hooks.registered[k]&&(i(V).rootPropertyValueCache[H]=S.Normalizations.registered[H]?S.Normalizations.registered[H]("extract",null,L[1]):L[1]),"transform"===L[0]&&(C=!0)}}u.mobileHA&&i(V).transformCache.translate3d===a&&(i(V).transformCache.translate3d="(0px, 0px, 0px)",C=!0),C&&S.flushTransformCache(V)}}u.display!==a&&"none"!==u.display&&(b.State.calls[o][2].display=!1),u.visibility!==a&&"hidden"!==u.visibility&&(b.State.calls[o][2].visibility=!1),u.progress&&u.progress.call(s[1],s[1],h,Math.max(0,d+u.duration-t),d,y),1===h&&p(o)}}b.State.isTicking&&w(c)}function p(e,t){if(!b.State.calls[e])return!1;for(var r=b.State.calls[e][0],n=b.State.calls[e][1],o=b.State.calls[e][2],s=b.State.calls[e][4],l=!1,u=0,c=r.length;c>u;u++){var p=r[u].element;if(t||o.loop||("none"===o.display&&S.setPropertyValue(p,"display",o.display),"hidden"===o.visibility&&S.setPropertyValue(p,"visibility",o.visibility)),o.loop!==!0&&(f.queue(p)[1]===a||!/\.velocityQueueEntryFlag/i.test(f.queue(p)[1]))&&i(p)){i(p).isAnimating=!1,i(p).rootPropertyValueCache={};var d=!1;f.each(S.Lists.transforms3D,function(e,t){var r=/^scale/.test(t)?1:0,n=i(p).transformCache[t];i(p).transformCache[t]!==a&&new RegExp("^\\("+r+"[^.]").test(n)&&(d=!0,delete i(p).transformCache[t])}),o.mobileHA&&(d=!0,delete i(p).transformCache.translate3d),d&&S.flushTransformCache(p),S.Values.removeClass(p,"velocity-animating")}if(!t&&o.complete&&!o.loop&&u===c-1)try{o.complete.call(n,n)}catch(g){setTimeout(function(){throw g},1)}s&&o.loop!==!0&&s(n),i(p)&&o.loop===!0&&!t&&(f.each(i(p).tweensContainer,function(e,t){/^rotate/.test(e)&&360===parseFloat(t.endValue)&&(t.endValue=0,t.startValue=360),/^backgroundPosition/.test(e)&&100===parseFloat(t.endValue)&&"%"===t.unitType&&(t.endValue=0,t.startValue=100)}),b(p,"reverse",{loop:!0,delay:o.delay})),o.queue!==!1&&f.dequeue(p,o.queue)}b.State.calls[e]=!1;for(var m=0,y=b.State.calls.length;y>m;m++)if(b.State.calls[m]!==!1){l=!0;break}l===!1&&(b.State.isTicking=!1,delete b.State.calls,b.State.calls=[])}var f,d=function(){if(r.documentMode)return r.documentMode;for(var e=7;e>4;e--){var t=r.createElement("div");if(t.innerHTML="<!--[if IE "+e+"]><span></span><![endif]-->",t.getElementsByTagName("span").length)return t=null,e}return a}(),g=function(){var e=0;return t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||function(t){var r,a=(new Date).getTime();return r=Math.max(0,16-(a-e)),e=a+r,setTimeout(function(){t(a+r)},r)}}(),m={isString:function(e){return"string"==typeof e},isArray:Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},isFunction:function(e){return"[object Function]"===Object.prototype.toString.call(e)},isNode:function(e){return e&&e.nodeType},isNodeList:function(e){return"object"==typeof e&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(e))&&e.length!==a&&(0===e.length||"object"==typeof e[0]&&e[0].nodeType>0)},isWrapped:function(e){return e&&(e.jquery||t.Zepto&&t.Zepto.zepto.isZ(e))},isSVG:function(e){return t.SVGElement&&e instanceof t.SVGElement},isEmptyObject:function(e){for(var t in e)return!1;return!0}},y=!1;if(e.fn&&e.fn.jquery?(f=e,y=!0):f=t.Velocity.Utilities,8>=d&&!y)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(7>=d)return void(jQuery.fn.velocity=jQuery.fn.animate);var h=400,v="swing",b={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:t.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:r.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[]},CSS:{},Utilities:f,Redirects:{},Easings:{},Promise:t.Promise,defaults:{queue:"",duration:h,easing:v,begin:a,complete:a,progress:a,display:a,visibility:a,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0},init:function(e){f.data(e,"velocity",{isSVG:m.isSVG(e),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:2,patch:2},debug:!1};t.pageYOffset!==a?(b.State.scrollAnchor=t,b.State.scrollPropertyLeft="pageXOffset",b.State.scrollPropertyTop="pageYOffset"):(b.State.scrollAnchor=r.documentElement||r.body.parentNode||r.body,b.State.scrollPropertyLeft="scrollLeft",b.State.scrollPropertyTop="scrollTop");var x=function(){function e(e){return-e.tension*e.x-e.friction*e.v}function t(t,r,a){var n={x:t.x+a.dx*r,v:t.v+a.dv*r,tension:t.tension,friction:t.friction};return{dx:n.v,dv:e(n)}}function r(r,a){var n={dx:r.v,dv:e(r)},o=t(r,.5*a,n),i=t(r,.5*a,o),s=t(r,a,i),l=1/6*(n.dx+2*(o.dx+i.dx)+s.dx),u=1/6*(n.dv+2*(o.dv+i.dv)+s.dv);return r.x=r.x+l*a,r.v=r.v+u*a,r}return function a(e,t,n){var o,i,s,l={x:-1,v:0,tension:null,friction:null},u=[0],c=0,p=1e-4,f=.016;for(e=parseFloat(e)||500,t=parseFloat(t)||20,n=n||null,l.tension=e,l.friction=t,o=null!==n,o?(c=a(e,t),i=c/n*f):i=f;s=r(s||l,i),u.push(1+s.x),c+=16,Math.abs(s.x)>p&&Math.abs(s.v)>p;);return o?function(e){return u[e*(u.length-1)|0]}:c}}();b.Easings={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},spring:function(e){return 1-Math.cos(4.5*e*Math.PI)*Math.exp(6*-e)}},f.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(e,t){b.Easings[t[0]]=l.apply(null,t[1])});var S=b.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"]},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var e=0;e<S.Lists.colors.length;e++){var t="color"===S.Lists.colors[e]?"0 0 0 1":"255 255 255 1";S.Hooks.templates[S.Lists.colors[e]]=["Red Green Blue Alpha",t]}var r,a,n;if(d)for(r in S.Hooks.templates){a=S.Hooks.templates[r],n=a[0].split(" ");var o=a[1].match(S.RegEx.valueSplit);"Color"===n[0]&&(n.push(n.shift()),o.push(o.shift()),S.Hooks.templates[r]=[n.join(" "),o.join(" ")])}for(r in S.Hooks.templates){a=S.Hooks.templates[r],n=a[0].split(" ");for(var e in n){var i=r+n[e],s=e;S.Hooks.registered[i]=[r,s]}}},getRoot:function(e){var t=S.Hooks.registered[e];return t?t[0]:e},cleanRootPropertyValue:function(e,t){return S.RegEx.valueUnwrap.test(t)&&(t=t.match(S.RegEx.valueUnwrap)[1]),S.Values.isCSSNullValue(t)&&(t=S.Hooks.templates[e][1]),t},extractValue:function(e,t){var r=S.Hooks.registered[e];if(r){var a=r[0],n=r[1];return t=S.Hooks.cleanRootPropertyValue(a,t),t.toString().match(S.RegEx.valueSplit)[n]}return t},injectValue:function(e,t,r){var a=S.Hooks.registered[e];if(a){var n,o,i=a[0],s=a[1];return r=S.Hooks.cleanRootPropertyValue(i,r),n=r.toString().match(S.RegEx.valueSplit),n[s]=t,o=n.join(" ")}return r}},Normalizations:{registered:{clip:function(e,t,r){switch(e){case"name":return"clip";case"extract":var a;return S.RegEx.wrappedValueAlreadyExtracted.test(r)?a=r:(a=r.toString().match(S.RegEx.valueUnwrap),a=a?a[1].replace(/,(\s+)?/g," "):r),a;case"inject":return"rect("+r+")"}},blur:function(e,t,r){switch(e){case"name":return b.State.isFirefox?"filter":"-webkit-filter";case"extract":var a=parseFloat(r);if(!a&&0!==a){var n=r.toString().match(/blur\(([0-9]+[A-z]+)\)/i);a=n?n[1]:0}return a;case"inject":return parseFloat(r)?"blur("+r+")":"none"}},opacity:function(e,t,r){if(8>=d)switch(e){case"name":return"filter";case"extract":var a=r.toString().match(/alpha\(opacity=(.*)\)/i);return r=a?a[1]/100:1;case"inject":return t.style.zoom=1,parseFloat(r)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(r),10)+")"}else switch(e){case"name":return"opacity";case"extract":return r;case"inject":return r}}},register:function(){9>=d||b.State.isGingerbread||(S.Lists.transformsBase=S.Lists.transformsBase.concat(S.Lists.transforms3D));for(var e=0;e<S.Lists.transformsBase.length;e++)!function(){var t=S.Lists.transformsBase[e];S.Normalizations.registered[t]=function(e,r,n){switch(e){case"name":return"transform";case"extract":return i(r)===a||i(r).transformCache[t]===a?/^scale/i.test(t)?1:0:i(r).transformCache[t].replace(/[()]/g,"");case"inject":var o=!1;switch(t.substr(0,t.length-1)){case"translate":o=!/(%|px|em|rem|vw|vh|\d)$/i.test(n);break;case"scal":case"scale":b.State.isAndroid&&i(r).transformCache[t]===a&&1>n&&(n=1),o=!/(\d)$/i.test(n);break;case"skew":o=!/(deg|\d)$/i.test(n);break;case"rotate":o=!/(deg|\d)$/i.test(n)}return o||(i(r).transformCache[t]="("+n+")"),i(r).transformCache[t]}}}();for(var e=0;e<S.Lists.colors.length;e++)!function(){var t=S.Lists.colors[e];S.Normalizations.registered[t]=function(e,r,n){switch(e){case"name":return t;case"extract":var o;if(S.RegEx.wrappedValueAlreadyExtracted.test(n))o=n;else{var i,s={black:"rgb(0, 0, 0)",blue:"rgb(0, 0, 255)",gray:"rgb(128, 128, 128)",green:"rgb(0, 128, 0)",red:"rgb(255, 0, 0)",white:"rgb(255, 255, 255)"};/^[A-z]+$/i.test(n)?i=s[n]!==a?s[n]:s.black:S.RegEx.isHex.test(n)?i="rgb("+S.Values.hexToRgb(n).join(" ")+")":/^rgba?\(/i.test(n)||(i=s.black),o=(i||n).toString().match(S.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g," ")}return 8>=d||3!==o.split(" ").length||(o+=" 1"),o;case"inject":return 8>=d?4===n.split(" ").length&&(n=n.split(/\s+/).slice(0,3).join(" ")):3===n.split(" ").length&&(n+=" 1"),(8>=d?"rgb":"rgba")+"("+n.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")"}}}()}},Names:{camelCase:function(e){return e.replace(/-(\w)/g,function(e,t){return t.toUpperCase()})},SVGAttribute:function(e){var t="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(d||b.State.isAndroid&&!b.State.isChrome)&&(t+="|transform"),new RegExp("^("+t+")$","i").test(e)},prefixCheck:function(e){if(b.State.prefixMatches[e])return[b.State.prefixMatches[e],!0];for(var t=["","Webkit","Moz","ms","O"],r=0,a=t.length;a>r;r++){var n;if(n=0===r?e:t[r]+e.replace(/^\w/,function(e){return e.toUpperCase()}),m.isString(b.State.prefixElement.style[n]))return b.State.prefixMatches[e]=n,[n,!0]}return[e,!1]}},Values:{hexToRgb:function(e){var t,r=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,a=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;return e=e.replace(r,function(e,t,r,a){return t+t+r+r+a+a}),t=a.exec(e),t?[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]:[0,0,0]},isCSSNullValue:function(e){return 0==e||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(e)},getUnitType:function(e){return/^(rotate|skew)/i.test(e)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(e)?"":"px"},getDisplayType:function(e){var t=e&&e.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(t)?"inline":/^(li)$/i.test(t)?"list-item":/^(tr)$/i.test(t)?"table-row":/^(table)$/i.test(t)?"table":/^(tbody)$/i.test(t)?"table-row-group":"block"},addClass:function(e,t){e.classList?e.classList.add(t):e.className+=(e.className.length?" ":"")+t},removeClass:function(e,t){e.classList?e.classList.remove(t):e.className=e.className.toString().replace(new RegExp("(^|\\s)"+t.split(" ").join("|")+"(\\s|$)","gi")," ")}},getPropertyValue:function(e,r,n,o){function s(e,r){function n(){u&&S.setPropertyValue(e,"display","none")}var l=0;if(8>=d)l=f.css(e,r);else{var u=!1;if(/^(width|height)$/.test(r)&&0===S.getPropertyValue(e,"display")&&(u=!0,S.setPropertyValue(e,"display",S.Values.getDisplayType(e))),!o){if("height"===r&&"border-box"!==S.getPropertyValue(e,"boxSizing").toString().toLowerCase()){var c=e.offsetHeight-(parseFloat(S.getPropertyValue(e,"borderTopWidth"))||0)-(parseFloat(S.getPropertyValue(e,"borderBottomWidth"))||0)-(parseFloat(S.getPropertyValue(e,"paddingTop"))||0)-(parseFloat(S.getPropertyValue(e,"paddingBottom"))||0);return n(),c}if("width"===r&&"border-box"!==S.getPropertyValue(e,"boxSizing").toString().toLowerCase()){var p=e.offsetWidth-(parseFloat(S.getPropertyValue(e,"borderLeftWidth"))||0)-(parseFloat(S.getPropertyValue(e,"borderRightWidth"))||0)-(parseFloat(S.getPropertyValue(e,"paddingLeft"))||0)-(parseFloat(S.getPropertyValue(e,"paddingRight"))||0);return n(),p}}var g;g=i(e)===a?t.getComputedStyle(e,null):i(e).computedStyle?i(e).computedStyle:i(e).computedStyle=t.getComputedStyle(e,null),"borderColor"===r&&(r="borderTopColor"),l=9===d&&"filter"===r?g.getPropertyValue(r):g[r],(""===l||null===l)&&(l=e.style[r]),n()}if("auto"===l&&/^(top|right|bottom|left)$/i.test(r)){var m=s(e,"position");("fixed"===m||"absolute"===m&&/top|left/i.test(r))&&(l=f(e).position()[r]+"px")}return l}var l;if(S.Hooks.registered[r]){var u=r,c=S.Hooks.getRoot(u);n===a&&(n=S.getPropertyValue(e,S.Names.prefixCheck(c)[0])),S.Normalizations.registered[c]&&(n=S.Normalizations.registered[c]("extract",e,n)),l=S.Hooks.extractValue(u,n)}else if(S.Normalizations.registered[r]){var p,g;p=S.Normalizations.registered[r]("name",e),"transform"!==p&&(g=s(e,S.Names.prefixCheck(p)[0]),S.Values.isCSSNullValue(g)&&S.Hooks.templates[r]&&(g=S.Hooks.templates[r][1])),l=S.Normalizations.registered[r]("extract",e,g)}if(!/^[\d-]/.test(l))if(i(e)&&i(e).isSVG&&S.Names.SVGAttribute(r))if(/^(height|width)$/i.test(r))try{l=e.getBBox()[r]}catch(m){l=0}else l=e.getAttribute(r);else l=s(e,S.Names.prefixCheck(r)[0]);return S.Values.isCSSNullValue(l)&&(l=0),b.debug>=2&&console.log("Get "+r+": "+l),l},setPropertyValue:function(e,r,a,n,o){var s=r;if("scroll"===r)o.container?o.container["scroll"+o.direction]=a:"Left"===o.direction?t.scrollTo(a,o.alternateValue):t.scrollTo(o.alternateValue,a);else if(S.Normalizations.registered[r]&&"transform"===S.Normalizations.registered[r]("name",e))S.Normalizations.registered[r]("inject",e,a),s="transform",a=i(e).transformCache[r];else{if(S.Hooks.registered[r]){var l=r,u=S.Hooks.getRoot(r);n=n||S.getPropertyValue(e,u),a=S.Hooks.injectValue(l,a,n),r=u}if(S.Normalizations.registered[r]&&(a=S.Normalizations.registered[r]("inject",e,a),r=S.Normalizations.registered[r]("name",e)),s=S.Names.prefixCheck(r)[0],8>=d)try{e.style[s]=a}catch(c){b.debug&&console.log("Browser does not support ["+a+"] for ["+s+"]")}else i(e)&&i(e).isSVG&&S.Names.SVGAttribute(r)?e.setAttribute(r,a):e.style[s]=a;b.debug>=2&&console.log("Set "+r+" ("+s+"): "+a)}return[s,a]},flushTransformCache:function(e){function t(t){return parseFloat(S.getPropertyValue(e,t))}var r="";if((d||b.State.isAndroid&&!b.State.isChrome)&&i(e).isSVG){var a={translate:[t("translateX"),t("translateY")],skewX:[t("skewX")],skewY:[t("skewY")],scale:1!==t("scale")?[t("scale"),t("scale")]:[t("scaleX"),t("scaleY")],rotate:[t("rotateZ"),0,0]};f.each(i(e).transformCache,function(e){/^translate/i.test(e)?e="translate":/^scale/i.test(e)?e="scale":/^rotate/i.test(e)&&(e="rotate"),a[e]&&(r+=e+"("+a[e].join(" ")+") ",delete a[e])})}else{var n,o;f.each(i(e).transformCache,function(t){return n=i(e).transformCache[t],"transformPerspective"===t?(o=n,!0):(9===d&&"rotateZ"===t&&(t="rotate"),void(r+=t+n+" "))}),o&&(r="perspective"+o+" "+r)}S.setPropertyValue(e,"transform",r)}};S.Hooks.register(),S.Normalizations.register(),b.hook=function(e,t,r){var n=a;return e=o(e),f.each(e,function(e,o){if(i(o)===a&&b.init(o),r===a)n===a&&(n=b.CSS.getPropertyValue(o,t));else{var s=b.CSS.setPropertyValue(o,t,r);"transform"===s[0]&&b.CSS.flushTransformCache(o),n=s}}),n};var P=function(){function e(){return s?k.promise||null:l}function n(){function e(e){function p(e,t){var r=a,n=a,i=a;return m.isArray(e)?(r=e[0],!m.isArray(e[1])&&/^[\d-]/.test(e[1])||m.isFunction(e[1])||S.RegEx.isHex.test(e[1])?i=e[1]:(m.isString(e[1])&&!S.RegEx.isHex.test(e[1])||m.isArray(e[1]))&&(n=t?e[1]:u(e[1],s.duration),e[2]!==a&&(i=e[2]))):r=e,t||(n=n||s.easing),m.isFunction(r)&&(r=r.call(o,V,w)),m.isFunction(i)&&(i=i.call(o,V,w)),[r||0,n,i]}function d(e,t){var r,a;return a=(t||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(e){return r=e,""}),r||(r=S.Values.getUnitType(e)),[a,r]}function h(){var e={myParent:o.parentNode||r.body,position:S.getPropertyValue(o,"position"),fontSize:S.getPropertyValue(o,"fontSize")},a=e.position===L.lastPosition&&e.myParent===L.lastParent,n=e.fontSize===L.lastFontSize;L.lastParent=e.myParent,L.lastPosition=e.position,L.lastFontSize=e.fontSize;var s=100,l={};if(n&&a)l.emToPx=L.lastEmToPx,l.percentToPxWidth=L.lastPercentToPxWidth,l.percentToPxHeight=L.lastPercentToPxHeight;else{var u=i(o).isSVG?r.createElementNS("http://www.w3.org/2000/svg","rect"):r.createElement("div");b.init(u),e.myParent.appendChild(u),f.each(["overflow","overflowX","overflowY"],function(e,t){b.CSS.setPropertyValue(u,t,"hidden")}),b.CSS.setPropertyValue(u,"position",e.position),b.CSS.setPropertyValue(u,"fontSize",e.fontSize),b.CSS.setPropertyValue(u,"boxSizing","content-box"),f.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(e,t){b.CSS.setPropertyValue(u,t,s+"%")}),b.CSS.setPropertyValue(u,"paddingLeft",s+"em"),l.percentToPxWidth=L.lastPercentToPxWidth=(parseFloat(S.getPropertyValue(u,"width",null,!0))||1)/s,l.percentToPxHeight=L.lastPercentToPxHeight=(parseFloat(S.getPropertyValue(u,"height",null,!0))||1)/s,l.emToPx=L.lastEmToPx=(parseFloat(S.getPropertyValue(u,"paddingLeft"))||1)/s,e.myParent.removeChild(u)}return null===L.remToPx&&(L.remToPx=parseFloat(S.getPropertyValue(r.body,"fontSize"))||16),null===L.vwToPx&&(L.vwToPx=parseFloat(t.innerWidth)/100,L.vhToPx=parseFloat(t.innerHeight)/100),l.remToPx=L.remToPx,l.vwToPx=L.vwToPx,l.vhToPx=L.vhToPx,b.debug>=1&&console.log("Unit ratios: "+JSON.stringify(l),o),l}if(s.begin&&0===V)try{s.begin.call(g,g)}catch(x){setTimeout(function(){throw x},1)}if("scroll"===A){var P,C,T,F=/^x$/i.test(s.axis)?"Left":"Top",j=parseFloat(s.offset)||0;s.container?m.isWrapped(s.container)||m.isNode(s.container)?(s.container=s.container[0]||s.container,P=s.container["scroll"+F],T=P+f(o).position()[F.toLowerCase()]+j):s.container=null:(P=b.State.scrollAnchor[b.State["scrollProperty"+F]],C=b.State.scrollAnchor[b.State["scrollProperty"+("Left"===F?"Top":"Left")]],T=f(o).offset()[F.toLowerCase()]+j),l={scroll:{rootPropertyValue:!1,startValue:P,currentValue:P,endValue:T,unitType:"",easing:s.easing,scrollData:{container:s.container,direction:F,alternateValue:C}},element:o},b.debug&&console.log("tweensContainer (scroll): ",l.scroll,o)}else if("reverse"===A){if(!i(o).tweensContainer)return void f.dequeue(o,s.queue);"none"===i(o).opts.display&&(i(o).opts.display="auto"),"hidden"===i(o).opts.visibility&&(i(o).opts.visibility="visible"),i(o).opts.loop=!1,i(o).opts.begin=null,i(o).opts.complete=null,v.easing||delete s.easing,v.duration||delete s.duration,s=f.extend({},i(o).opts,s);var E=f.extend(!0,{},i(o).tweensContainer);for(var H in E)if("element"!==H){var N=E[H].startValue;E[H].startValue=E[H].currentValue=E[H].endValue,E[H].endValue=N,m.isEmptyObject(v)||(E[H].easing=s.easing),b.debug&&console.log("reverse tweensContainer ("+H+"): "+JSON.stringify(E[H]),o)}l=E}else if("start"===A){var E;i(o).tweensContainer&&i(o).isAnimating===!0&&(E=i(o).tweensContainer),f.each(y,function(e,t){if(RegExp("^"+S.Lists.colors.join("$|^")+"$").test(e)){var r=p(t,!0),n=r[0],o=r[1],i=r[2];if(S.RegEx.isHex.test(n)){for(var s=["Red","Green","Blue"],l=S.Values.hexToRgb(n),u=i?S.Values.hexToRgb(i):a,c=0;c<s.length;c++){var f=[l[c]];o&&f.push(o),u!==a&&f.push(u[c]),y[e+s[c]]=f}delete y[e]}}});for(var z in y){var O=p(y[z]),q=O[0],$=O[1],M=O[2];z=S.Names.camelCase(z);var I=S.Hooks.getRoot(z),B=!1;if(i(o).isSVG||"tween"===I||S.Names.prefixCheck(I)[1]!==!1||S.Normalizations.registered[I]!==a){(s.display!==a&&null!==s.display&&"none"!==s.display||s.visibility!==a&&"hidden"!==s.visibility)&&/opacity|filter/.test(z)&&!M&&0!==q&&(M=0),s._cacheValues&&E&&E[z]?(M===a&&(M=E[z].endValue+E[z].unitType),B=i(o).rootPropertyValueCache[I]):S.Hooks.registered[z]?M===a?(B=S.getPropertyValue(o,I),M=S.getPropertyValue(o,z,B)):B=S.Hooks.templates[I][1]:M===a&&(M=S.getPropertyValue(o,z));var W,G,Y,D=!1;if(W=d(z,M),M=W[0],Y=W[1],W=d(z,q),q=W[0].replace(/^([+-\/*])=/,function(e,t){return D=t,""}),G=W[1],M=parseFloat(M)||0,q=parseFloat(q)||0,"%"===G&&(/^(fontSize|lineHeight)$/.test(z)?(q/=100,G="em"):/^scale/.test(z)?(q/=100,G=""):/(Red|Green|Blue)$/i.test(z)&&(q=q/100*255,G="")),/[\/*]/.test(D))G=Y;else if(Y!==G&&0!==M)if(0===q)G=Y;else{n=n||h();var Q=/margin|padding|left|right|width|text|word|letter/i.test(z)||/X$/.test(z)||"x"===z?"x":"y";switch(Y){case"%":M*="x"===Q?n.percentToPxWidth:n.percentToPxHeight;break;case"px":break;default:M*=n[Y+"ToPx"]}switch(G){case"%":M*=1/("x"===Q?n.percentToPxWidth:n.percentToPxHeight);break;case"px":break;default:M*=1/n[G+"ToPx"]}}switch(D){case"+":q=M+q;break;case"-":q=M-q;break;case"*":q=M*q;break;case"/":q=M/q}l[z]={rootPropertyValue:B,startValue:M,currentValue:M,endValue:q,unitType:G,easing:$},b.debug&&console.log("tweensContainer ("+z+"): "+JSON.stringify(l[z]),o)}else b.debug&&console.log("Skipping ["+I+"] due to a lack of browser support.")}l.element=o}l.element&&(S.Values.addClass(o,"velocity-animating"),R.push(l),""===s.queue&&(i(o).tweensContainer=l,i(o).opts=s),i(o).isAnimating=!0,V===w-1?(b.State.calls.push([R,g,s,null,k.resolver]),b.State.isTicking===!1&&(b.State.isTicking=!0,c())):V++)}var n,o=this,s=f.extend({},b.defaults,v),l={};switch(i(o)===a&&b.init(o),parseFloat(s.delay)&&s.queue!==!1&&f.queue(o,s.queue,function(e){b.velocityQueueEntryFlag=!0,i(o).delayTimer={setTimeout:setTimeout(e,parseFloat(s.delay)),next:e}}),s.duration.toString().toLowerCase()){case"fast":s.duration=200;break;case"normal":s.duration=h;break;case"slow":s.duration=600;break;default:s.duration=parseFloat(s.duration)||1}b.mock!==!1&&(b.mock===!0?s.duration=s.delay=1:(s.duration*=parseFloat(b.mock)||1,s.delay*=parseFloat(b.mock)||1)),s.easing=u(s.easing,s.duration),s.begin&&!m.isFunction(s.begin)&&(s.begin=null),s.progress&&!m.isFunction(s.progress)&&(s.progress=null),s.complete&&!m.isFunction(s.complete)&&(s.complete=null),s.display!==a&&null!==s.display&&(s.display=s.display.toString().toLowerCase(),"auto"===s.display&&(s.display=b.CSS.Values.getDisplayType(o))),s.visibility!==a&&null!==s.visibility&&(s.visibility=s.visibility.toString().toLowerCase()),s.mobileHA=s.mobileHA&&b.State.isMobile&&!b.State.isGingerbread,s.queue===!1?s.delay?setTimeout(e,s.delay):e():f.queue(o,s.queue,function(t,r){return r===!0?(k.promise&&k.resolver(g),!0):(b.velocityQueueEntryFlag=!0,void e(t))}),""!==s.queue&&"fx"!==s.queue||"inprogress"===f.queue(o)[0]||f.dequeue(o)}var s,l,d,g,y,v,x=arguments[0]&&(arguments[0].p||f.isPlainObject(arguments[0].properties)&&!arguments[0].properties.names||m.isString(arguments[0].properties));if(m.isWrapped(this)?(s=!1,d=0,g=this,l=this):(s=!0,d=1,g=x?arguments[0].elements||arguments[0].e:arguments[0]),g=o(g)){x?(y=arguments[0].properties||arguments[0].p,v=arguments[0].options||arguments[0].o):(y=arguments[d],v=arguments[d+1]);var w=g.length,V=0;if(!/^(stop|finish)$/i.test(y)&&!f.isPlainObject(v)){var C=d+1;v={};for(var T=C;T<arguments.length;T++)m.isArray(arguments[T])||!/^(fast|normal|slow)$/i.test(arguments[T])&&!/^\d/.test(arguments[T])?m.isString(arguments[T])||m.isArray(arguments[T])?v.easing=arguments[T]:m.isFunction(arguments[T])&&(v.complete=arguments[T]):v.duration=arguments[T]}var k={promise:null,resolver:null,rejecter:null};s&&b.Promise&&(k.promise=new b.Promise(function(e,t){k.resolver=e,k.rejecter=t}));var A;switch(y){case"scroll":A="scroll";break;case"reverse":A="reverse";break;case"finish":case"stop":f.each(g,function(e,t){i(t)&&i(t).delayTimer&&(clearTimeout(i(t).delayTimer.setTimeout),i(t).delayTimer.next&&i(t).delayTimer.next(),delete i(t).delayTimer)});var F=[];return f.each(b.State.calls,function(e,t){t&&f.each(t[1],function(r,n){var o=v===a?"":v;return o===!0||t[2].queue===o||v===a&&t[2].queue===!1?void f.each(g,function(r,a){a===n&&((v===!0||m.isString(v))&&(f.each(f.queue(a,m.isString(v)?v:""),function(e,t){
+m.isFunction(t)&&t(null,!0)}),f.queue(a,m.isString(v)?v:"",[])),"stop"===y?(i(a)&&i(a).tweensContainer&&o!==!1&&f.each(i(a).tweensContainer,function(e,t){t.endValue=t.currentValue}),F.push(e)):"finish"===y&&(t[2].duration=1))}):!0})}),"stop"===y&&(f.each(F,function(e,t){p(t,!0)}),k.promise&&k.resolver(g)),e();default:if(!f.isPlainObject(y)||m.isEmptyObject(y)){if(m.isString(y)&&b.Redirects[y]){var j=f.extend({},v),E=j.duration,H=j.delay||0;return j.backwards===!0&&(g=f.extend(!0,[],g).reverse()),f.each(g,function(e,t){parseFloat(j.stagger)?j.delay=H+parseFloat(j.stagger)*e:m.isFunction(j.stagger)&&(j.delay=H+j.stagger.call(t,e,w)),j.drag&&(j.duration=parseFloat(E)||(/^(callout|transition)/.test(y)?1e3:h),j.duration=Math.max(j.duration*(j.backwards?1-e/w:(e+1)/w),.75*j.duration,200)),b.Redirects[y].call(t,t,j||{},e,w,g,k.promise?k:a)}),e()}var N="Velocity: First argument ("+y+") was not a property map, a known action, or a registered redirect. Aborting.";return k.promise?k.rejecter(new Error(N)):console.log(N),e()}A="start"}var L={lastParent:null,lastPosition:null,lastFontSize:null,lastPercentToPxWidth:null,lastPercentToPxHeight:null,lastEmToPx:null,remToPx:null,vwToPx:null,vhToPx:null},R=[];f.each(g,function(e,t){m.isNode(t)&&n.call(t)});var z,j=f.extend({},b.defaults,v);if(j.loop=parseInt(j.loop),z=2*j.loop-1,j.loop)for(var O=0;z>O;O++){var q={delay:j.delay,progress:j.progress};O===z-1&&(q.display=j.display,q.visibility=j.visibility,q.complete=j.complete),P(g,"reverse",q)}return e()}};b=f.extend(P,b),b.animate=P;var w=t.requestAnimationFrame||g;return b.State.isMobile||r.hidden===a||r.addEventListener("visibilitychange",function(){r.hidden?(w=function(e){return setTimeout(function(){e(!0)},16)},c()):w=t.requestAnimationFrame||g}),e.Velocity=b,e!==t&&(e.fn.velocity=P,e.fn.velocity.defaults=b.defaults),f.each(["Down","Up"],function(e,t){b.Redirects["slide"+t]=function(e,r,n,o,i,s){var l=f.extend({},r),u=l.begin,c=l.complete,p={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""},d={};l.display===a&&(l.display="Down"===t?"inline"===b.CSS.Values.getDisplayType(e)?"inline-block":"block":"none"),l.begin=function(){u&&u.call(i,i);for(var r in p){d[r]=e.style[r];var a=b.CSS.getPropertyValue(e,r);p[r]="Down"===t?[a,0]:[0,a]}d.overflow=e.style.overflow,e.style.overflow="hidden"},l.complete=function(){for(var t in d)e.style[t]=d[t];c&&c.call(i,i),s&&s.resolver(i)},b(e,p,l)}}),f.each(["In","Out"],function(e,t){b.Redirects["fade"+t]=function(e,r,n,o,i,s){var l=f.extend({},r),u={opacity:"In"===t?1:0},c=l.complete;l.complete=n!==o-1?l.begin=null:function(){c&&c.call(i,i),s&&s.resolver(i)},l.display===a&&(l.display="In"===t?"auto":"none"),b(this,u,l)}}),b}(window.jQuery||window.Zepto||window,window,document)}));
+;!function(a,b,c,d){"use strict";function k(a,b,c){return setTimeout(q(a,c),b)}function l(a,b,c){return Array.isArray(a)?(m(a,c[b],c),!0):!1}function m(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e<a.length;)b.call(c,a[e],e,a),e++;else for(e in a)a.hasOwnProperty(e)&&b.call(c,a[e],e,a)}function n(a,b,c){for(var e=Object.keys(b),f=0;f<e.length;)(!c||c&&a[e[f]]===d)&&(a[e[f]]=b[e[f]]),f++;return a}function o(a,b){return n(a,b,!0)}function p(a,b,c){var e,d=b.prototype;e=a.prototype=Object.create(d),e.constructor=a,e._super=d,c&&n(e,c)}function q(a,b){return function(){return a.apply(b,arguments)}}function r(a,b){return typeof a==g?a.apply(b?b[0]||d:d,b):a}function s(a,b){return a===d?b:a}function t(a,b,c){m(x(b),function(b){a.addEventListener(b,c,!1)})}function u(a,b,c){m(x(b),function(b){a.removeEventListener(b,c,!1)})}function v(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function w(a,b){return a.indexOf(b)>-1}function x(a){return a.trim().split(/\s+/g)}function y(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;d<a.length;){if(c&&a[d][c]==b||!c&&a[d]===b)return d;d++}return-1}function z(a){return Array.prototype.slice.call(a,0)}function A(a,b,c){for(var d=[],e=[],f=0;f<a.length;){var g=b?a[f][b]:a[f];y(e,g)<0&&d.push(a[f]),e[f]=g,f++}return c&&(d=b?d.sort(function(a,c){return a[b]>c[b]}):d.sort()),d}function B(a,b){for(var c,f,g=b[0].toUpperCase()+b.slice(1),h=0;h<e.length;){if(c=e[h],f=c?c+g:b,f in a)return f;h++}return d}function D(){return C++}function E(a){var b=a.ownerDocument;return b.defaultView||b.parentWindow}function ab(a,b){var c=this;this.manager=a,this.callback=b,this.element=a.element,this.target=a.options.inputTarget,this.domHandler=function(b){r(a.options.enable,[a])&&c.handler(b)},this.init()}function bb(a){var b,c=a.options.inputClass;return b=c?c:H?wb:I?Eb:G?Gb:rb,new b(a,cb)}function cb(a,b,c){var d=c.pointers.length,e=c.changedPointers.length,f=b&O&&0===d-e,g=b&(Q|R)&&0===d-e;c.isFirst=!!f,c.isFinal=!!g,f&&(a.session={}),c.eventType=b,db(a,c),a.emit("hammer.input",c),a.recognize(c),a.session.prevInput=c}function db(a,b){var c=a.session,d=b.pointers,e=d.length;c.firstInput||(c.firstInput=gb(b)),e>1&&!c.firstMultiple?c.firstMultiple=gb(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=hb(d);b.timeStamp=j(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=lb(h,i),b.distance=kb(h,i),eb(c,b),b.offsetDirection=jb(b.deltaX,b.deltaY),b.scale=g?nb(g.pointers,d):1,b.rotation=g?mb(g.pointers,d):0,fb(c,b);var k=a.element;v(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function eb(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};(b.eventType===O||f.eventType===Q)&&(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function fb(a,b){var f,g,h,j,c=a.lastInterval||b,e=b.timeStamp-c.timeStamp;if(b.eventType!=R&&(e>N||c.velocity===d)){var k=c.deltaX-b.deltaX,l=c.deltaY-b.deltaY,m=ib(e,k,l);g=m.x,h=m.y,f=i(m.x)>i(m.y)?m.x:m.y,j=jb(k,l),a.lastInterval=b}else f=c.velocity,g=c.velocityX,h=c.velocityY,j=c.direction;b.velocity=f,b.velocityX=g,b.velocityY=h,b.direction=j}function gb(a){for(var b=[],c=0;c<a.pointers.length;)b[c]={clientX:h(a.pointers[c].clientX),clientY:h(a.pointers[c].clientY)},c++;return{timeStamp:j(),pointers:b,center:hb(b),deltaX:a.deltaX,deltaY:a.deltaY}}function hb(a){var b=a.length;if(1===b)return{x:h(a[0].clientX),y:h(a[0].clientY)};for(var c=0,d=0,e=0;b>e;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:h(c/b),y:h(d/b)}}function ib(a,b,c){return{x:b/a||0,y:c/a||0}}function jb(a,b){return a===b?S:i(a)>=i(b)?a>0?T:U:b>0?V:W}function kb(a,b,c){c||(c=$);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function lb(a,b,c){c||(c=$);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function mb(a,b){return lb(b[1],b[0],_)-lb(a[1],a[0],_)}function nb(a,b){return kb(b[0],b[1],_)/kb(a[0],a[1],_)}function rb(){this.evEl=pb,this.evWin=qb,this.allow=!0,this.pressed=!1,ab.apply(this,arguments)}function wb(){this.evEl=ub,this.evWin=vb,ab.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function Ab(){this.evTarget=yb,this.evWin=zb,this.started=!1,ab.apply(this,arguments)}function Bb(a,b){var c=z(a.touches),d=z(a.changedTouches);return b&(Q|R)&&(c=A(c.concat(d),"identifier",!0)),[c,d]}function Eb(){this.evTarget=Db,this.targetIds={},ab.apply(this,arguments)}function Fb(a,b){var c=z(a.touches),d=this.targetIds;if(b&(O|P)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=z(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return v(a.target,i)}),b===O)for(e=0;e<f.length;)d[f[e].identifier]=!0,e++;for(e=0;e<g.length;)d[g[e].identifier]&&h.push(g[e]),b&(Q|R)&&delete d[g[e].identifier],e++;return h.length?[A(f.concat(h),"identifier",!0),h]:void 0}function Gb(){ab.apply(this,arguments);var a=q(this.handler,this);this.touch=new Eb(this.manager,a),this.mouse=new rb(this.manager,a)}function Pb(a,b){this.manager=a,this.set(b)}function Qb(a){if(w(a,Mb))return Mb;var b=w(a,Nb),c=w(a,Ob);return b&&c?Nb+" "+Ob:b||c?b?Nb:Ob:w(a,Lb)?Lb:Kb}function Yb(a){this.id=D(),this.manager=null,this.options=o(a||{},this.defaults),this.options.enable=s(this.options.enable,!0),this.state=Rb,this.simultaneous={},this.requireFail=[]}function Zb(a){return a&Wb?"cancel":a&Ub?"end":a&Tb?"move":a&Sb?"start":""}function $b(a){return a==W?"down":a==V?"up":a==T?"left":a==U?"right":""}function _b(a,b){var c=b.manager;return c?c.get(a):a}function ac(){Yb.apply(this,arguments)}function bc(){ac.apply(this,arguments),this.pX=null,this.pY=null}function cc(){ac.apply(this,arguments)}function dc(){Yb.apply(this,arguments),this._timer=null,this._input=null}function ec(){ac.apply(this,arguments)}function fc(){ac.apply(this,arguments)}function gc(){Yb.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function hc(a,b){return b=b||{},b.recognizers=s(b.recognizers,hc.defaults.preset),new kc(a,b)}function kc(a,b){b=b||{},this.options=o(b,hc.defaults),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.element=a,this.input=bb(this),this.touchAction=new Pb(this,this.options.touchAction),lc(this,!0),m(b.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function lc(a,b){var c=a.element;m(a.options.cssProps,function(a,d){c.style[B(c.style,d)]=b?a:""})}function mc(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var e=["","webkit","moz","MS","ms","o"],f=b.createElement("div"),g="function",h=Math.round,i=Math.abs,j=Date.now,C=1,F=/mobile|tablet|ip(ad|hone|od)|android/i,G="ontouchstart"in a,H=B(a,"PointerEvent")!==d,I=G&&F.test(navigator.userAgent),J="touch",K="pen",L="mouse",M="kinect",N=25,O=1,P=2,Q=4,R=8,S=1,T=2,U=4,V=8,W=16,X=T|U,Y=V|W,Z=X|Y,$=["x","y"],_=["clientX","clientY"];ab.prototype={handler:function(){},init:function(){this.evEl&&t(this.element,this.evEl,this.domHandler),this.evTarget&&t(this.target,this.evTarget,this.domHandler),this.evWin&&t(E(this.element),this.evWin,this.domHandler)},destroy:function(){this.evEl&&u(this.element,this.evEl,this.domHandler),this.evTarget&&u(this.target,this.evTarget,this.domHandler),this.evWin&&u(E(this.element),this.evWin,this.domHandler)}};var ob={mousedown:O,mousemove:P,mouseup:Q},pb="mousedown",qb="mousemove mouseup";p(rb,ab,{handler:function(a){var b=ob[a.type];b&O&&0===a.button&&(this.pressed=!0),b&P&&1!==a.which&&(b=Q),this.pressed&&this.allow&&(b&Q&&(this.pressed=!1),this.callback(this.manager,b,{pointers:[a],changedPointers:[a],pointerType:L,srcEvent:a}))}});var sb={pointerdown:O,pointermove:P,pointerup:Q,pointercancel:R,pointerout:R},tb={2:J,3:K,4:L,5:M},ub="pointerdown",vb="pointermove pointerup pointercancel";a.MSPointerEvent&&(ub="MSPointerDown",vb="MSPointerMove MSPointerUp MSPointerCancel"),p(wb,ab,{handler:function(a){var b=this.store,c=!1,d=a.type.toLowerCase().replace("ms",""),e=sb[d],f=tb[a.pointerType]||a.pointerType,g=f==J,h=y(b,a.pointerId,"pointerId");e&O&&(0===a.button||g)?0>h&&(b.push(a),h=b.length-1):e&(Q|R)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var xb={touchstart:O,touchmove:P,touchend:Q,touchcancel:R},yb="touchstart",zb="touchstart touchmove touchend touchcancel";p(Ab,ab,{handler:function(a){var b=xb[a.type];if(b===O&&(this.started=!0),this.started){var c=Bb.call(this,a,b);b&(Q|R)&&0===c[0].length-c[1].length&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:J,srcEvent:a})}}});var Cb={touchstart:O,touchmove:P,touchend:Q,touchcancel:R},Db="touchstart touchmove touchend touchcancel";p(Eb,ab,{handler:function(a){var b=Cb[a.type],c=Fb.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:J,srcEvent:a})}}),p(Gb,ab,{handler:function(a,b,c){var d=c.pointerType==J,e=c.pointerType==L;if(d)this.mouse.allow=!1;else if(e&&!this.mouse.allow)return;b&(Q|R)&&(this.mouse.allow=!0),this.callback(a,b,c)},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var Hb=B(f.style,"touchAction"),Ib=Hb!==d,Jb="compute",Kb="auto",Lb="manipulation",Mb="none",Nb="pan-x",Ob="pan-y";Pb.prototype={set:function(a){a==Jb&&(a=this.compute()),Ib&&(this.manager.element.style[Hb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return m(this.manager.recognizers,function(b){r(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),Qb(a.join(" "))},preventDefaults:function(a){if(!Ib){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return b.preventDefault(),void 0;var d=this.actions,e=w(d,Mb),f=w(d,Ob),g=w(d,Nb);return e||f&&c&X||g&&c&Y?this.preventSrc(b):void 0}},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var Rb=1,Sb=2,Tb=4,Ub=8,Vb=Ub,Wb=16,Xb=32;Yb.prototype={defaults:{},set:function(a){return n(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(l(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_b(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return l(a,"dropRecognizeWith",this)?this:(a=_b(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(l(a,"requireFailure",this))return this;var b=this.requireFail;return a=_b(a,this),-1===y(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(l(a,"dropRequireFailure",this))return this;a=_b(a,this);var b=y(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function d(d){b.manager.emit(b.options.event+(d?Zb(c):""),a)}var b=this,c=this.state;Ub>c&&d(!0),d(),c>=Ub&&d(!0)},tryEmit:function(a){return this.canEmit()?this.emit(a):(this.state=Xb,void 0)},canEmit:function(){for(var a=0;a<this.requireFail.length;){if(!(this.requireFail[a].state&(Xb|Rb)))return!1;a++}return!0},recognize:function(a){var b=n({},a);return r(this.options.enable,[this,b])?(this.state&(Vb|Wb|Xb)&&(this.state=Rb),this.state=this.process(b),this.state&(Sb|Tb|Ub|Wb)&&this.tryEmit(b),void 0):(this.reset(),this.state=Xb,void 0)},process:function(){},getTouchAction:function(){},reset:function(){}},p(ac,Yb,{defaults:{pointers:1},attrTest:function(a){var b=this.options.pointers;return 0===b||a.pointers.length===b},process:function(a){var b=this.state,c=a.eventType,d=b&(Sb|Tb),e=this.attrTest(a);return d&&(c&R||!e)?b|Wb:d||e?c&Q?b|Ub:b&Sb?b|Tb:Sb:Xb}}),p(bc,ac,{defaults:{event:"pan",threshold:10,pointers:1,direction:Z},getTouchAction:function(){var a=this.options.direction,b=[];return a&X&&b.push(Ob),a&Y&&b.push(Nb),b},directionTest:function(a){var b=this.options,c=!0,d=a.distance,e=a.direction,f=a.deltaX,g=a.deltaY;return e&b.direction||(b.direction&X?(e=0===f?S:0>f?T:U,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?S:0>g?V:W,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return ac.prototype.attrTest.call(this,a)&&(this.state&Sb||!(this.state&Sb)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$b(a.direction);b&&this.manager.emit(this.options.event+b,a),this._super.emit.call(this,a)}}),p(cc,ac,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[Mb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&Sb)},emit:function(a){if(this._super.emit.call(this,a),1!==a.scale){var b=a.scale<1?"in":"out";this.manager.emit(this.options.event+b,a)}}}),p(dc,Yb,{defaults:{event:"press",pointers:1,time:500,threshold:5},getTouchAction:function(){return[Kb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance<b.threshold,e=a.deltaTime>b.time;if(this._input=a,!d||!c||a.eventType&(Q|R)&&!e)this.reset();else if(a.eventType&O)this.reset(),this._timer=k(function(){this.state=Vb,this.tryEmit()},b.time,this);else if(a.eventType&Q)return Vb;return Xb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===Vb&&(a&&a.eventType&Q?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=j(),this.manager.emit(this.options.event,this._input)))}}),p(ec,ac,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[Mb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&Sb)}}),p(fc,ac,{defaults:{event:"swipe",threshold:10,velocity:.65,direction:X|Y,pointers:1},getTouchAction:function(){return bc.prototype.getTouchAction.call(this)},attrTest:function(a){var c,b=this.options.direction;return b&(X|Y)?c=a.velocity:b&X?c=a.velocityX:b&Y&&(c=a.velocityY),this._super.attrTest.call(this,a)&&b&a.direction&&a.distance>this.options.threshold&&i(c)>this.options.velocity&&a.eventType&Q},emit:function(a){var b=$b(a.direction);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),p(gc,Yb,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:2,posThreshold:10},getTouchAction:function(){return[Lb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance<b.threshold,e=a.deltaTime<b.time;if(this.reset(),a.eventType&O&&0===this.count)return this.failTimeout();if(d&&e&&c){if(a.eventType!=Q)return this.failTimeout();var f=this.pTime?a.timeStamp-this.pTime<b.interval:!0,g=!this.pCenter||kb(this.pCenter,a.center)<b.posThreshold;this.pTime=a.timeStamp,this.pCenter=a.center,g&&f?this.count+=1:this.count=1,this._input=a;var h=this.count%b.taps;if(0===h)return this.hasRequireFailures()?(this._timer=k(function(){this.state=Vb,this.tryEmit()},b.interval,this),Sb):Vb}return Xb},failTimeout:function(){return this._timer=k(function(){this.state=Xb},this.options.interval,this),Xb},reset:function(){clearTimeout(this._timer)},emit:function(){this.state==Vb&&(this._input.tapCount=this.count,this.manager.emit(this.options.event,this._input))}}),hc.VERSION="2.0.4",hc.defaults={domEvents:!1,touchAction:Jb,enable:!0,inputTarget:null,inputClass:null,preset:[[ec,{enable:!1}],[cc,{enable:!1},["rotate"]],[fc,{direction:X}],[bc,{direction:X},["swipe"]],[gc],[gc,{event:"doubletap",taps:2},["tap"]],[dc]],cssProps:{userSelect:"default",touchSelect:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}};var ic=1,jc=2;kc.prototype={set:function(a){return n(this.options,a),a.touchAction&&this.touchAction.update(),a.inputTarget&&(this.input.destroy(),this.input.target=a.inputTarget,this.input.init()),this},stop:function(a){this.session.stopped=a?jc:ic},recognize:function(a){var b=this.session;if(!b.stopped){this.touchAction.preventDefaults(a);var c,d=this.recognizers,e=b.curRecognizer;(!e||e&&e.state&Vb)&&(e=b.curRecognizer=null);for(var f=0;f<d.length;)c=d[f],b.stopped===jc||e&&c!=e&&!c.canRecognizeWith(e)?c.reset():c.recognize(a),!e&&c.state&(Sb|Tb|Ub)&&(e=b.curRecognizer=c),f++}},get:function(a){if(a instanceof Yb)return a;for(var b=this.recognizers,c=0;c<b.length;c++)if(b[c].options.event==a)return b[c];return null},add:function(a){if(l(a,"add",this))return this;var b=this.get(a.options.event);return b&&this.remove(b),this.recognizers.push(a),a.manager=this,this.touchAction.update(),a},remove:function(a){if(l(a,"remove",this))return this;var b=this.recognizers;return a=this.get(a),b.splice(y(b,a),1),this.touchAction.update(),this},on:function(a,b){var c=this.handlers;return m(x(a),function(a){c[a]=c[a]||[],c[a].push(b)}),this},off:function(a,b){var c=this.handlers;return m(x(a),function(a){b?c[a].splice(y(c[a],b),1):delete c[a]}),this},emit:function(a,b){this.options.domEvents&&mc(a,b);var c=this.handlers[a]&&this.handlers[a].slice();if(c&&c.length){b.type=a,b.preventDefault=function(){b.srcEvent.preventDefault()};for(var d=0;d<c.length;)c[d](b),d++}},destroy:function(){this.element&&lc(this,!1),this.handlers={},this.session={},this.input.destroy(),this.element=null}},n(hc,{INPUT_START:O,INPUT_MOVE:P,INPUT_END:Q,INPUT_CANCEL:R,STATE_POSSIBLE:Rb,STATE_BEGAN:Sb,STATE_CHANGED:Tb,STATE_ENDED:Ub,STATE_RECOGNIZED:Vb,STATE_CANCELLED:Wb,STATE_FAILED:Xb,DIRECTION_NONE:S,DIRECTION_LEFT:T,DIRECTION_RIGHT:U,DIRECTION_UP:V,DIRECTION_DOWN:W,DIRECTION_HORIZONTAL:X,DIRECTION_VERTICAL:Y,DIRECTION_ALL:Z,Manager:kc,Input:ab,TouchAction:Pb,TouchInput:Eb,MouseInput:rb,PointerEventInput:wb,TouchMouseInput:Gb,SingleTouchInput:Ab,Recognizer:Yb,AttrRecognizer:ac,Tap:gc,Pan:bc,Swipe:fc,Pinch:cc,Rotate:ec,Press:dc,on:t,off:u,each:m,merge:o,extend:n,inherit:p,bindFn:q,prefixed:B}),typeof define==g&&define.amd?define(function(){return hc}):"undefined"!=typeof module&&module.exports?module.exports=hc:a[c]=hc}(window,document,"Hammer");;(function(factory) {
+    if (typeof define === 'function' && define.amd) {
+        define(['jquery', 'hammerjs'], factory);
+    } else if (typeof exports === 'object') {
+        factory(require('jquery'), require('hammerjs'));
+    } else {
+        factory(jQuery, Hammer);
+    }
+}(function($, Hammer) {
+    function hammerify(el, options) {
+        var $el = $(el);
+        if(!$el.data("hammer")) {
+            $el.data("hammer", new Hammer($el[0], options));
+        }
+    }
+
+    $.fn.hammer = function(options) {
+        return this.each(function() {
+            hammerify(this, options);
+        });
+    };
+
+    // extend the emit method to also trigger jQuery events
+    Hammer.Manager.prototype.emit = (function(originalEmit) {
+        return function(type, data) {
+            originalEmit.call(this, type, data);
+            $(this.element).trigger({
+                type: type,
+                gesture: data
+            });
+        };
+    })(Hammer.Manager.prototype.emit);
+}));
+;// Required for Meteor package, the use of window prevents export by Meteor
+(function(window){
+  if(window.Package){
+    Materialize = {};
+  } else {
+    window.Materialize = {};
+  }
+})(window);
+
+
+/*
+ * raf.js
+ * https://github.com/ngryman/raf.js
+ *
+ * original requestAnimationFrame polyfill by Erik Möller
+ * inspired from paul_irish gist and post
+ *
+ * Copyright (c) 2013 ngryman
+ * Licensed under the MIT license.
+ */
+(function(window) {
+  var lastTime = 0,
+    vendors = ['webkit', 'moz'],
+    requestAnimationFrame = window.requestAnimationFrame,
+    cancelAnimationFrame = window.cancelAnimationFrame,
+    i = vendors.length;
+
+  // try to un-prefix existing raf
+  while (--i >= 0 && !requestAnimationFrame) {
+    requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame'];
+    cancelAnimationFrame = window[vendors[i] + 'CancelRequestAnimationFrame'];
+  }
+
+  // polyfill with setTimeout fallback
+  // heavily inspired from @darius gist mod: https://gist.github.com/paulirish/1579671#comment-837945
+  if (!requestAnimationFrame || !cancelAnimationFrame) {
+    requestAnimationFrame = function(callback) {
+      var now = +Date.now(),
+        nextTime = Math.max(lastTime + 16, now);
+      return setTimeout(function() {
+        callback(lastTime = nextTime);
+      }, nextTime - now);
+    };
+
+    cancelAnimationFrame = clearTimeout;
+  }
+
+  // export to window
+  window.requestAnimationFrame = requestAnimationFrame;
+  window.cancelAnimationFrame = cancelAnimationFrame;
+}(window));
+
+
+// Unique ID
+Materialize.guid = (function() {
+  function s4() {
+    return Math.floor((1 + Math.random()) * 0x10000)
+      .toString(16)
+      .substring(1);
+  }
+  return function() {
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+           s4() + '-' + s4() + s4() + s4();
+  };
+})();
+
+/**
+ * Escapes hash from special characters
+ * @param {string} hash  String returned from this.hash
+ * @returns {string}
+ */
+Materialize.escapeHash = function(hash) {
+  return hash.replace( /(:|\.|\[|\]|,|=)/g, "\\$1" );
+};
+
+Materialize.elementOrParentIsFixed = function(element) {
+    var $element = $(element);
+    var $checkElements = $element.add($element.parents());
+    var isFixed = false;
+    $checkElements.each(function(){
+        if ($(this).css("position") === "fixed") {
+            isFixed = true;
+            return false;
+        }
+    });
+    return isFixed;
+};
+
+
+/**
+ * Get time in ms
+ * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
+ * @type {function}
+ * @return {number}
+ */
+var getTime = (Date.now || function () {
+  return new Date().getTime();
+});
+
+
+/**
+ * Returns a function, that, when invoked, will only be triggered at most once
+ * during a given window of time. Normally, the throttled function will run
+ * as much as it can, without ever going more than once per `wait` duration;
+ * but if you'd like to disable the execution on the leading edge, pass
+ * `{leading: false}`. To disable execution on the trailing edge, ditto.
+ * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
+ * @param {function} func
+ * @param {number} wait
+ * @param {Object=} options
+ * @returns {Function}
+ */
+Materialize.throttle = function(func, wait, options) {
+  var context, args, result;
+  var timeout = null;
+  var previous = 0;
+  options || (options = {});
+  var later = function () {
+    previous = options.leading === false ? 0 : getTime();
+    timeout = null;
+    result = func.apply(context, args);
+    context = args = null;
+  };
+  return function () {
+    var now = getTime();
+    if (!previous && options.leading === false) previous = now;
+    var remaining = wait - (now - previous);
+    context = this;
+    args = arguments;
+    if (remaining <= 0) {
+      clearTimeout(timeout);
+      timeout = null;
+      previous = now;
+      result = func.apply(context, args);
+      context = args = null;
+    } else if (!timeout && options.trailing !== false) {
+      timeout = setTimeout(later, remaining);
+    }
+    return result;
+  };
+};
+
+
+// Velocity has conflicts when loaded with jQuery, this will check for it
+// First, check if in noConflict mode
+var Vel;
+if (jQuery) {
+  Vel = jQuery.Velocity;
+} else if ($) {
+  Vel = $.Velocity;
+} else {
+  Vel = Velocity;
+}
+;(function ($) {
+  $.fn.collapsible = function(options) {
+    var defaults = {
+      accordion: undefined,
+      onOpen: undefined,
+      onClose: undefined
+    };
+
+    options = $.extend(defaults, options);
+
+
+    return this.each(function() {
+
+      var $this = $(this);
+
+      var $panel_headers = $(this).find('> li > .collapsible-header');
+
+      var collapsible_type = $this.data("collapsible");
+
+      // Turn off any existing event handlers
+      $this.off('click.collapse', '> li > .collapsible-header');
+      $panel_headers.off('click.collapse');
+
+
+      /****************
+      Helper Functions
+      ****************/
+
+      // Accordion Open
+      function accordionOpen(object) {
+        $panel_headers = $this.find('> li > .collapsible-header');
+        if (object.hasClass('active')) {
+          object.parent().addClass('active');
+        }
+        else {
+          object.parent().removeClass('active');
+        }
+        if (object.parent().hasClass('active')){
+          object.siblings('.collapsible-body').stop(true,false).slideDown({ duration: 350, easing: "easeOutQuart", queue: false, complete: function() {$(this).css('height', '');}});
+        }
+        else{
+          object.siblings('.collapsible-body').stop(true,false).slideUp({ duration: 350, easing: "easeOutQuart", queue: false, complete: function() {$(this).css('height', '');}});
+        }
+
+        $panel_headers.not(object).removeClass('active').parent().removeClass('active');
+
+        // Close previously open accordion elements.
+        $panel_headers.not(object).parent().children('.collapsible-body').stop(true,false).each(function() {
+          if ($(this).is(':visible')) {
+            $(this).slideUp({
+              duration: 350,
+              easing: "easeOutQuart",
+              queue: false,
+              complete:
+                function() {
+                  $(this).css('height', '');
+                  execCallbacks($(this).siblings('.collapsible-header'));
+                }
+            });
+          }
+        });
+      }
+
+      // Expandable Open
+      function expandableOpen(object) {
+        if (object.hasClass('active')) {
+          object.parent().addClass('active');
+        }
+        else {
+          object.parent().removeClass('active');
+        }
+        if (object.parent().hasClass('active')){
+          object.siblings('.collapsible-body').stop(true,false).slideDown({ duration: 350, easing: "easeOutQuart", queue: false, complete: function() {$(this).css('height', '');}});
+        }
+        else {
+          object.siblings('.collapsible-body').stop(true,false).slideUp({ duration: 350, easing: "easeOutQuart", queue: false, complete: function() {$(this).css('height', '');}});
+        }
+      }
+
+      // Open collapsible. object: .collapsible-header
+      function collapsibleOpen(object) {
+        if (options.accordion || collapsible_type === "accordion" || collapsible_type === undefined) { // Handle Accordion
+          accordionOpen(object);
+        } else { // Handle Expandables
+          expandableOpen(object);
+        }
+
+        execCallbacks(object);
+      }
+
+      // Handle callbacks
+      function execCallbacks(object) {
+        if (object.hasClass('active')) {
+          if (typeof(options.onOpen) === "function") {
+            options.onOpen.call(this, object.parent());
+          }
+        } else {
+          if (typeof(options.onClose) === "function") {
+            options.onClose.call(this, object.parent());
+          }
+        }
+      }
+
+      /**
+       * Check if object is children of panel header
+       * @param  {Object}  object Jquery object
+       * @return {Boolean} true if it is children
+       */
+      function isChildrenOfPanelHeader(object) {
+
+        var panelHeader = getPanelHeader(object);
+
+        return panelHeader.length > 0;
+      }
+
+      /**
+       * Get panel header from a children element
+       * @param  {Object} object Jquery object
+       * @return {Object} panel header object
+       */
+      function getPanelHeader(object) {
+
+        return object.closest('li > .collapsible-header');
+      }
+
+      /*****  End Helper Functions  *****/
+
+
+
+      // Add click handler to only direct collapsible header children
+      $this.on('click.collapse', '> li > .collapsible-header', function(e) {
+        var element = $(e.target);
+
+        if (isChildrenOfPanelHeader(element)) {
+          element = getPanelHeader(element);
+        }
+
+        element.toggleClass('active');
+
+        collapsibleOpen(element);
+      });
+
+
+      // Open first active
+      if (options.accordion || collapsible_type === "accordion" || collapsible_type === undefined) { // Handle Accordion
+        collapsibleOpen($panel_headers.filter('.active').first());
+
+      } else { // Handle Expandables
+        $panel_headers.filter('.active').each(function() {
+          collapsibleOpen($(this));
+        });
+      }
+
+    });
+  };
+
+  $(document).ready(function(){
+    $('.collapsible').collapsible();
+  });
+}( jQuery ));;(function ($) {
+
+  // Add posibility to scroll to selected option
+  // usefull for select for example
+  $.fn.scrollTo = function(elem) {
+    $(this).scrollTop($(this).scrollTop() - $(this).offset().top + $(elem).offset().top);
+    return this;
+  };
+
+  $.fn.dropdown = function (options) {
+    var defaults = {
+      inDuration: 300,
+      outDuration: 225,
+      constrainWidth: true, // Constrains width of dropdown to the activator
+      hover: false,
+      gutter: 0, // Spacing from edge
+      belowOrigin: false,
+      alignment: 'left',
+      stopPropagation: false
+    };
+
+    // Open dropdown.
+    if (options === "open") {
+      this.each(function() {
+        $(this).trigger('open');
+      });
+      return false;
+    }
+
+    // Close dropdown.
+    if (options === "close") {
+      this.each(function() {
+        $(this).trigger('close');
+      });
+      return false;
+    }
+
+    this.each(function(){
+      var origin = $(this);
+      var curr_options = $.extend({}, defaults, options);
+      var isFocused = false;
+
+      // Dropdown menu
+      var activates = $("#"+ origin.attr('data-activates'));
+
+      function updateOptions() {
+        if (origin.data('induration') !== undefined)
+          curr_options.inDuration = origin.data('induration');
+        if (origin.data('outduration') !== undefined)
+          curr_options.outDuration = origin.data('outduration');
+        if (origin.data('constrainwidth') !== undefined)
+          curr_options.constrainWidth = origin.data('constrainwidth');
+        if (origin.data('hover') !== undefined)
+          curr_options.hover = origin.data('hover');
+        if (origin.data('gutter') !== undefined)
+          curr_options.gutter = origin.data('gutter');
+        if (origin.data('beloworigin') !== undefined)
+          curr_options.belowOrigin = origin.data('beloworigin');
+        if (origin.data('alignment') !== undefined)
+          curr_options.alignment = origin.data('alignment');
+        if (origin.data('stoppropagation') !== undefined)
+          curr_options.stopPropagation = origin.data('stoppropagation');
+      }
+
+      updateOptions();
+
+      // Attach dropdown to its activator
+      origin.after(activates);
+
+      /*
+        Helper function to position and resize dropdown.
+        Used in hover and click handler.
+      */
+      function placeDropdown(eventType) {
+        // Check for simultaneous focus and click events.
+        if (eventType === 'focus') {
+          isFocused = true;
+        }
+
+        // Check html data attributes
+        updateOptions();
+
+        // Set Dropdown state
+        activates.addClass('active');
+        origin.addClass('active');
+
+        // Constrain width
+        if (curr_options.constrainWidth === true) {
+          activates.css('width', origin.outerWidth());
+
+        } else {
+          activates.css('white-space', 'nowrap');
+        }
+
+        // Offscreen detection
+        var windowHeight = window.innerHeight;
+        var originHeight = origin.innerHeight();
+        var offsetLeft = origin.offset().left;
+        var offsetTop = origin.offset().top - $(window).scrollTop();
+        var currAlignment = curr_options.alignment;
+        var gutterSpacing = 0;
+        var leftPosition = 0;
+
+        // Below Origin
+        var verticalOffset = 0;
+        if (curr_options.belowOrigin === true) {
+          verticalOffset = originHeight;
+        }
+
+        // Check for scrolling positioned container.
+        var scrollYOffset = 0;
+        var scrollXOffset = 0;
+        var wrapper = origin.parent();
+        if (!wrapper.is('body')) {
+          if (wrapper[0].scrollHeight > wrapper[0].clientHeight) {
+            scrollYOffset = wrapper[0].scrollTop;
+          }
+          if (wrapper[0].scrollWidth > wrapper[0].clientWidth) {
+            scrollXOffset = wrapper[0].scrollLeft;
+          }
+        }
+
+
+        if (offsetLeft + activates.innerWidth() > $(window).width()) {
+          // Dropdown goes past screen on right, force right alignment
+          currAlignment = 'right';
+
+        } else if (offsetLeft - activates.innerWidth() + origin.innerWidth() < 0) {
+          // Dropdown goes past screen on left, force left alignment
+          currAlignment = 'left';
+        }
+        // Vertical bottom offscreen detection
+        if (offsetTop + activates.innerHeight() > windowHeight) {
+          // If going upwards still goes offscreen, just crop height of dropdown.
+          if (offsetTop + originHeight - activates.innerHeight() < 0) {
+            var adjustedHeight = windowHeight - offsetTop - verticalOffset;
+            activates.css('max-height', adjustedHeight);
+          } else {
+            // Flow upwards.
+            if (!verticalOffset) {
+              verticalOffset += originHeight;
+            }
+            verticalOffset -= activates.innerHeight();
+          }
+        }
+
+        // Handle edge alignment
+        if (currAlignment === 'left') {
+          gutterSpacing = curr_options.gutter;
+          leftPosition = origin.position().left + gutterSpacing;
+        }
+        else if (currAlignment === 'right') {
+          var offsetRight = origin.position().left + origin.outerWidth() - activates.outerWidth();
+          gutterSpacing = -curr_options.gutter;
+          leftPosition =  offsetRight + gutterSpacing;
+        }
+
+        // Position dropdown
+        activates.css({
+          position: 'absolute',
+          top: origin.position().top + verticalOffset + scrollYOffset,
+          left: leftPosition + scrollXOffset
+        });
+
+
+        // Show dropdown
+        activates.stop(true, true).css('opacity', 0)
+          .slideDown({
+            queue: false,
+            duration: curr_options.inDuration,
+            easing: 'easeOutCubic',
+            complete: function() {
+              $(this).css('height', '');
+            }
+          })
+          .animate( {opacity: 1}, {queue: false, duration: curr_options.inDuration, easing: 'easeOutSine'});
+
+        // Add click close handler to document
+        $(document).bind('click.'+ activates.attr('id') + ' touchstart.' + activates.attr('id'), function (e) {
+          if (!activates.is(e.target) && !origin.is(e.target) && (!origin.find(e.target).length) ) {
+            hideDropdown();
+            $(document).unbind('click.'+ activates.attr('id') + ' touchstart.' + activates.attr('id'));
+          }
+        });
+      }
+
+      function hideDropdown() {
+        // Check for simultaneous focus and click events.
+        isFocused = false;
+        activates.fadeOut(curr_options.outDuration);
+        activates.removeClass('active');
+        origin.removeClass('active');
+        $(document).unbind('click.'+ activates.attr('id') + ' touchstart.' + activates.attr('id'));
+        setTimeout(function() { activates.css('max-height', ''); }, curr_options.outDuration);
+      }
+
+      // Hover
+      if (curr_options.hover) {
+        var open = false;
+        origin.unbind('click.' + origin.attr('id'));
+        // Hover handler to show dropdown
+        origin.on('mouseenter', function(e){ // Mouse over
+          if (open === false) {
+            placeDropdown();
+            open = true;
+          }
+        });
+        origin.on('mouseleave', function(e){
+          // If hover on origin then to something other than dropdown content, then close
+          var toEl = e.toElement || e.relatedTarget; // added browser compatibility for target element
+          if(!$(toEl).closest('.dropdown-content').is(activates)) {
+            activates.stop(true, true);
+            hideDropdown();
+            open = false;
+          }
+        });
+
+        activates.on('mouseleave', function(e){ // Mouse out
+          var toEl = e.toElement || e.relatedTarget;
+          if(!$(toEl).closest('.dropdown-button').is(origin)) {
+            activates.stop(true, true);
+            hideDropdown();
+            open = false;
+          }
+        });
+
+        // Click
+      } else {
+        // Click handler to show dropdown
+        origin.unbind('click.' + origin.attr('id'));
+        origin.bind('click.'+origin.attr('id'), function(e){
+          if (!isFocused) {
+            if ( origin[0] == e.currentTarget &&
+                 !origin.hasClass('active') &&
+                 ($(e.target).closest('.dropdown-content').length === 0)) {
+              e.preventDefault(); // Prevents button click from moving window
+              if (curr_options.stopPropagation) {
+                e.stopPropagation();
+              }
+              placeDropdown('click');
+            }
+            // If origin is clicked and menu is open, close menu
+            else if (origin.hasClass('active')) {
+              hideDropdown();
+              $(document).unbind('click.'+ activates.attr('id') + ' touchstart.' + activates.attr('id'));
+            }
+          }
+        });
+
+      } // End else
+
+      // Listen to open and close event - useful for select component
+      origin.on('open', function(e, eventType) {
+        placeDropdown(eventType);
+      });
+      origin.on('close', hideDropdown);
+
+
+    });
+  }; // End dropdown plugin
+
+  $(document).ready(function(){
+    $('.dropdown-button').dropdown();
+  });
+}( jQuery ));
+;(function($) {
+  var _stack = 0,
+  _lastID = 0,
+  _generateID = function() {
+    _lastID++;
+    return 'materialize-modal-overlay-' + _lastID;
+  };
+
+  var methods = {
+    init : function(options) {
+      var defaults = {
+        opacity: 0.5,
+        inDuration: 350,
+        outDuration: 250,
+        ready: undefined,
+        complete: undefined,
+        dismissible: true,
+        startingTop: '4%',
+        endingTop: '10%'
+      };
+
+      // Override defaults
+      options = $.extend(defaults, options);
+
+      return this.each(function() {
+        var $modal = $(this);
+        var modal_id = $(this).attr("id") || '#' + $(this).data('target');
+
+        var closeModal = function() {
+          var overlayID = $modal.data('overlay-id');
+          var $overlay = $('#' + overlayID);
+          $modal.removeClass('open');
+
+          // Enable scrolling
+          $('body').css({
+            overflow: '',
+            width: ''
+          });
+
+          $modal.find('.modal-close').off('click.close');
+          $(document).off('keyup.modal' + overlayID);
+
+          $overlay.velocity( { opacity: 0}, {duration: options.outDuration, queue: false, ease: "easeOutQuart"});
+
+
+          // Define Bottom Sheet animation
+          var exitVelocityOptions = {
+            duration: options.outDuration,
+            queue: false,
+            ease: "easeOutCubic",
+            // Handle modal ready callback
+            complete: function() {
+              $(this).css({display:"none"});
+
+              // Call complete callback
+              if (typeof(options.complete) === "function") {
+                options.complete.call(this, $modal);
+              }
+              $overlay.remove();
+              _stack--;
+            }
+          };
+          if ($modal.hasClass('bottom-sheet')) {
+            $modal.velocity({bottom: "-100%", opacity: 0}, exitVelocityOptions);
+          }
+          else {
+            $modal.velocity(
+              { top: options.startingTop, opacity: 0, scaleX: 0.7},
+              exitVelocityOptions
+            );
+          }
+        };
+
+        var openModal = function($trigger) {
+          var $body = $('body');
+          var oldWidth = $body.innerWidth();
+          $body.css('overflow', 'hidden');
+          $body.width(oldWidth);
+
+          if ($modal.hasClass('open')) {
+            return;
+          }
+
+          var overlayID = _generateID();
+          var $overlay = $('<div class="modal-overlay"></div>');
+          lStack = (++_stack);
+
+          // Store a reference of the overlay
+          $overlay.attr('id', overlayID).css('z-index', 1000 + lStack * 2);
+          $modal.data('overlay-id', overlayID).css('z-index', 1000 + lStack * 2 + 1);
+          $modal.addClass('open');
+
+          $("body").append($overlay);
+
+          if (options.dismissible) {
+            $overlay.click(function() {
+              closeModal();
+            });
+            // Return on ESC
+            $(document).on('keyup.modal' + overlayID, function(e) {
+              if (e.keyCode === 27) {   // ESC key
+                closeModal();
+              }
+            });
+          }
+
+          $modal.find(".modal-close").on('click.close', function(e) {
+            closeModal();
+          });
+
+          $overlay.css({ display : "block", opacity : 0 });
+
+          $modal.css({
+            display : "block",
+            opacity: 0
+          });
+
+          $overlay.velocity({opacity: options.opacity}, {duration: options.inDuration, queue: false, ease: "easeOutCubic"});
+          $modal.data('associated-overlay', $overlay[0]);
+
+          // Define Bottom Sheet animation
+          var enterVelocityOptions = {
+            duration: options.inDuration,
+            queue: false,
+            ease: "easeOutCubic",
+            // Handle modal ready callback
+            complete: function() {
+              if (typeof(options.ready) === "function") {
+                options.ready.call(this, $modal, $trigger);
+              }
+            }
+          };
+          if ($modal.hasClass('bottom-sheet')) {
+            $modal.velocity({bottom: "0", opacity: 1}, enterVelocityOptions);
+          }
+          else {
+            $.Velocity.hook($modal, "scaleX", 0.7);
+            $modal.css({ top: options.startingTop });
+            $modal.velocity({top: options.endingTop, opacity: 1, scaleX: '1'}, enterVelocityOptions);
+          }
+
+        };
+
+        // Reset handlers
+        $(document).off('click.modalTrigger', 'a[href="#' + modal_id + '"], [data-target="' + modal_id + '"]');
+        $(this).off('openModal');
+        $(this).off('closeModal');
+
+        // Close Handlers
+        $(document).on('click.modalTrigger', 'a[href="#' + modal_id + '"], [data-target="' + modal_id + '"]', function(e) {
+          options.startingTop = ($(this).offset().top - $(window).scrollTop()) /1.15;
+          openModal($(this));
+          e.preventDefault();
+        }); // done set on click
+
+        $(this).on('openModal', function() {
+          var modal_id = $(this).attr("href") || '#' + $(this).data('target');
+          openModal();
+        });
+
+        $(this).on('closeModal', function() {
+          closeModal();
+        });
+      }); // done return
+    },
+    open : function() {
+      $(this).trigger('openModal');
+    },
+    close : function() {
+      $(this).trigger('closeModal');
+    }
+  };
+
+  $.fn.modal = function(methodOrOptions) {
+    if ( methods[methodOrOptions] ) {
+      return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+      // Default to "init"
+      return methods.init.apply( this, arguments );
+    } else {
+      $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.modal' );
+    }
+  };
+})(jQuery);
+;(function ($) {
+
+  $.fn.materialbox = function () {
+
+    return this.each(function() {
+
+      if ($(this).hasClass('initialized')) {
+        return;
+      }
+
+      $(this).addClass('initialized');
+
+      var overlayActive = false;
+      var doneAnimating = true;
+      var inDuration = 275;
+      var outDuration = 200;
+      var origin = $(this);
+      var placeholder = $('<div></div>').addClass('material-placeholder');
+      var originalWidth = 0;
+      var originalHeight = 0;
+      var ancestorsChanged;
+      var ancestor;
+      origin.wrap(placeholder);
+
+
+      origin.on('click', function(){
+        var placeholder = origin.parent('.material-placeholder');
+        var windowWidth = window.innerWidth;
+        var windowHeight = window.innerHeight;
+        var originalWidth = origin.width();
+        var originalHeight = origin.height();
+
+
+        // If already modal, return to original
+        if (doneAnimating === false) {
+          returnToOriginal();
+          return false;
+        }
+        else if (overlayActive && doneAnimating===true) {
+          returnToOriginal();
+          return false;
+        }
+
+
+        // Set states
+        doneAnimating = false;
+        origin.addClass('active');
+        overlayActive = true;
+
+        // Set positioning for placeholder
+        placeholder.css({
+          width: placeholder[0].getBoundingClientRect().width,
+          height: placeholder[0].getBoundingClientRect().height,
+          position: 'relative',
+          top: 0,
+          left: 0
+        });
+
+        // Find ancestor with overflow: hidden; and remove it
+        ancestorsChanged = undefined;
+        ancestor = placeholder[0].parentNode;
+        var count = 0;
+        while (ancestor !== null && !$(ancestor).is(document)) {
+          var curr = $(ancestor);
+          if (curr.css('overflow') !== 'visible') {
+            curr.css('overflow', 'visible');
+            if (ancestorsChanged === undefined) {
+              ancestorsChanged = curr;
+            }
+            else {
+              ancestorsChanged = ancestorsChanged.add(curr);
+            }
+          }
+          ancestor = ancestor.parentNode;
+        }
+
+        // Set css on origin
+        origin.css({
+          position: 'absolute',
+          'z-index': 1000,
+          'will-change': 'left, top, width, height'
+        })
+        .data('width', originalWidth)
+        .data('height', originalHeight);
+
+        // Add overlay
+        var overlay = $('<div id="materialbox-overlay"></div>')
+          .css({
+            opacity: 0
+          })
+          .click(function(){
+            if (doneAnimating === true)
+            returnToOriginal();
+          });
+
+        // Put before in origin image to preserve z-index layering.
+        origin.before(overlay);
+
+        // Set dimensions if needed
+        var overlayOffset = overlay[0].getBoundingClientRect();
+        overlay.css({
+          width: windowWidth,
+          height: windowHeight,
+          left: -1 * overlayOffset.left,
+          top: -1 * overlayOffset.top
+        })
+
+        // Animate Overlay
+        overlay.velocity({opacity: 1},
+                           {duration: inDuration, queue: false, easing: 'easeOutQuad'} );
+
+        // Add and animate caption if it exists
+        if (origin.data('caption') !== "") {
+          var $photo_caption = $('<div class="materialbox-caption"></div>');
+          $photo_caption.text(origin.data('caption'));
+          $('body').append($photo_caption);
+          $photo_caption.css({ "display": "inline" });
+          $photo_caption.velocity({opacity: 1}, {duration: inDuration, queue: false, easing: 'easeOutQuad'});
+        }
+
+        // Resize Image
+        var ratio = 0;
+        var widthPercent = originalWidth / windowWidth;
+        var heightPercent = originalHeight / windowHeight;
+        var newWidth = 0;
+        var newHeight = 0;
+
+        if (widthPercent > heightPercent) {
+          ratio = originalHeight / originalWidth;
+          newWidth = windowWidth * 0.9;
+          newHeight = windowWidth * 0.9 * ratio;
+        }
+        else {
+          ratio = originalWidth / originalHeight;
+          newWidth = (windowHeight * 0.9) * ratio;
+          newHeight = windowHeight * 0.9;
+        }
+
+        // Animate image + set z-index
+        if(origin.hasClass('responsive-img')) {
+          origin.velocity({'max-width': newWidth, 'width': originalWidth}, {duration: 0, queue: false,
+            complete: function(){
+              origin.css({left: 0, top: 0})
+              .velocity(
+                {
+                  height: newHeight,
+                  width: newWidth,
+                  left: $(document).scrollLeft() + windowWidth/2 - origin.parent('.material-placeholder').offset().left - newWidth/2,
+                  top: $(document).scrollTop() + windowHeight/2 - origin.parent('.material-placeholder').offset().top - newHeight/ 2
+                },
+                {
+                  duration: inDuration,
+                  queue: false,
+                  easing: 'easeOutQuad',
+                  complete: function(){doneAnimating = true;}
+                }
+              );
+            } // End Complete
+          }); // End Velocity
+        }
+        else {
+          origin.css('left', 0)
+          .css('top', 0)
+          .velocity(
+            {
+              height: newHeight,
+              width: newWidth,
+              left: $(document).scrollLeft() + windowWidth/2 - origin.parent('.material-placeholder').offset().left - newWidth/2,
+              top: $(document).scrollTop() + windowHeight/2 - origin.parent('.material-placeholder').offset().top - newHeight/ 2
+            },
+            {
+              duration: inDuration,
+              queue: false,
+              easing: 'easeOutQuad',
+              complete: function(){doneAnimating = true;}
+            }
+            ); // End Velocity
+        }
+
+      }); // End origin on click
+
+
+      // Return on scroll
+      $(window).scroll(function() {
+        if (overlayActive) {
+          returnToOriginal();
+        }
+      });
+
+      // Return on ESC
+      $(document).keyup(function(e) {
+
+        if (e.keyCode === 27 && doneAnimating === true) {   // ESC key
+          if (overlayActive) {
+            returnToOriginal();
+          }
+        }
+      });
+
+
+      // This function returns the modaled image to the original spot
+      function returnToOriginal() {
+
+        doneAnimating = false;
+
+        var placeholder = origin.parent('.material-placeholder');
+        var windowWidth = window.innerWidth;
+        var windowHeight = window.innerHeight;
+        var originalWidth = origin.data('width');
+        var originalHeight = origin.data('height');
+
+        origin.velocity("stop", true);
+        $('#materialbox-overlay').velocity("stop", true);
+        $('.materialbox-caption').velocity("stop", true);
+
+
+        $('#materialbox-overlay').velocity({opacity: 0}, {
+          duration: outDuration, // Delay prevents animation overlapping
+          queue: false, easing: 'easeOutQuad',
+          complete: function(){
+            // Remove Overlay
+            overlayActive = false;
+            $(this).remove();
+          }
+        });
+
+        // Resize Image
+        origin.velocity(
+          {
+            width: originalWidth,
+            height: originalHeight,
+            left: 0,
+            top: 0
+          },
+          {
+            duration: outDuration,
+            queue: false, easing: 'easeOutQuad'
+          }
+        );
+
+        // Remove Caption + reset css settings on image
+        $('.materialbox-caption').velocity({opacity: 0}, {
+          duration: outDuration, // Delay prevents animation overlapping
+          queue: false, easing: 'easeOutQuad',
+          complete: function(){
+            placeholder.css({
+              height: '',
+              width: '',
+              position: '',
+              top: '',
+              left: ''
+            });
+
+            origin.css({
+              height: '',
+              top: '',
+              left: '',
+              width: '',
+              'max-width': '',
+              position: '',
+              'z-index': '',
+              'will-change': ''
+            });
+
+            // Remove class
+            origin.removeClass('active');
+            doneAnimating = true;
+            $(this).remove();
+
+            // Remove overflow overrides on ancestors
+            if (ancestorsChanged) {
+              ancestorsChanged.css('overflow', '');
+            }
+          }
+        });
+
+      }
+    });
+  };
+
+  $(document).ready(function(){
+    $('.materialboxed').materialbox();
+  });
+
+}( jQuery ));
+;(function ($) {
+
+  $.fn.parallax = function () {
+    var window_width = $(window).width();
+    // Parallax Scripts
+    return this.each(function(i) {
+      var $this = $(this);
+      $this.addClass('parallax');
+
+      function updateParallax(initial) {
+        var container_height;
+        if (window_width < 601) {
+          container_height = ($this.height() > 0) ? $this.height() : $this.children("img").height();
+        }
+        else {
+          container_height = ($this.height() > 0) ? $this.height() : 500;
+        }
+        var $img = $this.children("img").first();
+        var img_height = $img.height();
+        var parallax_dist = img_height - container_height;
+        var bottom = $this.offset().top + container_height;
+        var top = $this.offset().top;
+        var scrollTop = $(window).scrollTop();
+        var windowHeight = window.innerHeight;
+        var windowBottom = scrollTop + windowHeight;
+        var percentScrolled = (windowBottom - top) / (container_height + windowHeight);
+        var parallax = Math.round((parallax_dist * percentScrolled));
+
+        if (initial) {
+          $img.css('display', 'block');
+        }
+        if ((bottom > scrollTop) && (top < (scrollTop + windowHeight))) {
+          $img.css('transform', "translate3D(-50%," + parallax + "px, 0)");
+        }
+
+      }
+
+      // Wait for image load
+      $this.children("img").one("load", function() {
+        updateParallax(true);
+      }).each(function() {
+        if (this.complete) $(this).trigger("load");
+      });
+
+      $(window).scroll(function() {
+        window_width = $(window).width();
+        updateParallax(false);
+      });
+
+      $(window).resize(function() {
+        window_width = $(window).width();
+        updateParallax(false);
+      });
+
+    });
+
+  };
+}( jQuery ));
+;(function ($) {
+
+  var methods = {
+    init : function(options) {
+      var defaults = {
+        onShow: null,
+        swipeable: false,
+        responsiveThreshold: Infinity, // breakpoint for swipeable
+      };
+      options = $.extend(defaults, options);
+
+      return this.each(function() {
+
+      // For each set of tabs, we want to keep track of
+      // which tab is active and its associated content
+      var $this = $(this),
+          window_width = $(window).width();
+
+      var $active, $content, $links = $this.find('li.tab a'),
+          $tabs_width = $this.width(),
+          $tabs_content = $(),
+          $tabs_wrapper,
+          $tab_width = Math.max($tabs_width, $this[0].scrollWidth) / $links.length,
+          $indicator,
+          index = prev_index = 0,
+          clicked = false,
+          clickedTimeout,
+          transition = 300;
+
+
+      // Finds right attribute for indicator based on active tab.
+      // el: jQuery Object
+      var calcRightPos = function(el) {
+        return $tabs_width - el.position().left - el.outerWidth() - $this.scrollLeft();
+      };
+
+      // Finds left attribute for indicator based on active tab.
+      // el: jQuery Object
+      var calcLeftPos = function(el) {
+        return el.position().left + $this.scrollLeft();
+      };
+
+      // Animates Indicator to active tab.
+      // prev_index: Number
+      var animateIndicator = function(prev_index) {
+        if ((index - prev_index) >= 0) {
+          $indicator.velocity({"right": calcRightPos($active) }, { duration: transition, queue: false, easing: 'easeOutQuad'});
+          $indicator.velocity({"left": calcLeftPos($active) }, {duration: transition, queue: false, easing: 'easeOutQuad', delay: 90});
+
+        } else {
+          $indicator.velocity({"left": calcLeftPos($active) }, { duration: transition, queue: false, easing: 'easeOutQuad'});
+          $indicator.velocity({"right": calcRightPos($active) }, {duration: transition, queue: false, easing: 'easeOutQuad', delay: 90});
+        }
+      };
+
+      // Change swipeable according to responsive threshold
+      if (options.swipeable) {
+        if (window_width > options.responsiveThreshold) {
+          options.swipeable = false;
+        }
+      }
+
+
+      // If the location.hash matches one of the links, use that as the active tab.
+      $active = $($links.filter('[href="'+location.hash+'"]'));
+
+      // If no match is found, use the first link or any with class 'active' as the initial active tab.
+      if ($active.length === 0) {
+        $active = $(this).find('li.tab a.active').first();
+      }
+      if ($active.length === 0) {
+        $active = $(this).find('li.tab a').first();
+      }
+
+      $active.addClass('active');
+      index = $links.index($active);
+      if (index < 0) {
+        index = 0;
+      }
+
+      if ($active[0] !== undefined) {
+        $content = $($active[0].hash);
+        $content.addClass('active');
+      }
+
+      // append indicator then set indicator width to tab width
+      if (!$this.find('.indicator').length) {
+        $this.append('<div class="indicator"></div>');
+      }
+      $indicator = $this.find('.indicator');
+
+      // we make sure that the indicator is at the end of the tabs
+      $this.append($indicator);
+
+      if ($this.is(":visible")) {
+        // $indicator.css({"right": $tabs_width - ((index + 1) * $tab_width)});
+        // $indicator.css({"left": index * $tab_width});
+        setTimeout(function() {
+          $indicator.css({"right": calcRightPos($active) });
+          $indicator.css({"left": calcLeftPos($active) });
+        }, 0);
+      }
+      $(window).resize(function () {
+        $tabs_width = $this.width();
+        $tab_width = Math.max($tabs_width, $this[0].scrollWidth) / $links.length;
+        if (index < 0) {
+          index = 0;
+        }
+        if ($tab_width !== 0 && $tabs_width !== 0) {
+          $indicator.css({"right": calcRightPos($active) });
+          $indicator.css({"left": calcLeftPos($active) });
+        }
+      });
+
+      // Initialize Tabs Content.
+      if (options.swipeable) {
+        // TODO: Duplicate calls with swipeable? handle multiple div wrapping.
+        $links.each(function () {
+          var $curr_content = $(Materialize.escapeHash(this.hash));
+          $curr_content.addClass('carousel-item');
+          $tabs_content = $tabs_content.add($curr_content);
+        });
+        $tabs_wrapper = $tabs_content.wrapAll('<div class="tabs-content carousel"></div>');
+        $tabs_content.css('display', '');
+        $('.tabs-content.carousel').carousel({
+          fullWidth: true,
+          noWrap: true,
+          onCycleTo: function(item) {
+            if (!clicked) {
+              var prev_index = index;
+              index = $tabs_wrapper.index(item);
+              $active = $links.eq(index);
+              animateIndicator(prev_index);
+            }
+          },
+        });
+      } else {
+        // Hide the remaining content
+        $links.not($active).each(function () {
+          $(Materialize.escapeHash(this.hash)).hide();
+        });
+      }
+
+
+      // Bind the click event handler
+      $this.on('click', 'a', function(e) {
+        if ($(this).parent().hasClass('disabled')) {
+          e.preventDefault();
+          return;
+        }
+
+        // Act as regular link if target attribute is specified.
+        if (!!$(this).attr("target")) {
+          return;
+        }
+
+        clicked = true;
+        $tabs_width = $this.width();
+        $tab_width = Math.max($tabs_width, $this[0].scrollWidth) / $links.length;
+
+        // Make the old tab inactive.
+        $active.removeClass('active');
+        var $oldContent = $content
+
+        // Update the variables with the new link and content
+        $active = $(this);
+        $content = $(Materialize.escapeHash(this.hash));
+        $links = $this.find('li.tab a');
+        var activeRect = $active.position();
+
+        // Make the tab active.
+        $active.addClass('active');
+        prev_index = index;
+        index = $links.index($(this));
+        if (index < 0) {
+          index = 0;
+        }
+        // Change url to current tab
+        // window.location.hash = $active.attr('href');
+
+        // Swap content
+        if (options.swipeable) {
+          if ($tabs_content.length) {
+            $tabs_content.carousel('set', index);
+          }
+        } else {
+          if ($content !== undefined) {
+            $content.show();
+            $content.addClass('active');
+            if (typeof(options.onShow) === "function") {
+              options.onShow.call(this, $content);
+            }
+          }
+
+          if ($oldContent !== undefined &&
+              !$oldContent.is($content)) {
+            $oldContent.hide();
+            $oldContent.removeClass('active');
+          }
+        }
+
+        // Reset clicked state
+        clickedTimeout = setTimeout(function(){ clicked = false; }, transition);
+
+        // Update indicator
+        animateIndicator(prev_index);
+
+        // Prevent the anchor's default click action
+        e.preventDefault();
+      });
+    });
+
+    },
+    select_tab : function( id ) {
+      this.find('a[href="#' + id + '"]').trigger('click');
+    }
+  };
+
+  $.fn.tabs = function(methodOrOptions) {
+    if ( methods[methodOrOptions] ) {
+      return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+      // Default to "init"
+      return methods.init.apply( this, arguments );
+    } else {
+      $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.tabs' );
+    }
+  };
+
+  $(document).ready(function(){
+    $('ul.tabs').tabs();
+  });
+}( jQuery ));
+;(function ($) {
+    $.fn.tooltip = function (options) {
+      var timeout = null,
+      margin = 5;
+
+      // Defaults
+      var defaults = {
+        delay: 350,
+        tooltip: '',
+        position: 'bottom',
+        html: false
+      };
+
+      // Remove tooltip from the activator
+      if (options === "remove") {
+        this.each(function() {
+          $('#' + $(this).attr('data-tooltip-id')).remove();
+          $(this).off('mouseenter.tooltip mouseleave.tooltip');
+        });
+        return false;
+      }
+
+      options = $.extend(defaults, options);
+
+      return this.each(function() {
+        var tooltipId = Materialize.guid();
+        var origin = $(this);
+
+        // Destroy old tooltip
+        if (origin.attr('data-tooltip-id')) {
+          $('#' + origin.attr('data-tooltip-id')).remove();
+        }
+
+        origin.attr('data-tooltip-id', tooltipId);
+
+        // Get attributes.
+        var allowHtml,
+            tooltipDelay,
+            tooltipPosition,
+            tooltipText,
+            tooltipEl,
+            backdrop;
+        var setAttributes = function() {
+          allowHtml = origin.attr('data-html') ? origin.attr('data-html') === 'true' : options.html;
+          tooltipDelay = origin.attr('data-delay');
+          tooltipDelay = (tooltipDelay === undefined || tooltipDelay === '') ?
+              options.delay : tooltipDelay;
+          tooltipPosition = origin.attr('data-position');
+          tooltipPosition = (tooltipPosition === undefined || tooltipPosition === '') ?
+              options.position : tooltipPosition;
+          tooltipText = origin.attr('data-tooltip');
+          tooltipText = (tooltipText === undefined || tooltipText === '') ?
+              options.tooltip : tooltipText;
+        };
+        setAttributes();
+
+        var renderTooltipEl = function() {
+          var tooltip = $('<div class="material-tooltip"></div>');
+
+          // Create Text span
+          if (allowHtml) {
+            tooltipText = $('<span></span>').html(tooltipText);
+          } else{
+            tooltipText = $('<span></span>').text(tooltipText);
+          }
+
+          // Create tooltip
+          tooltip.append(tooltipText)
+            .appendTo($('body'))
+            .attr('id', tooltipId);
+
+          // Create backdrop
+          backdrop = $('<div class="backdrop"></div>');
+          backdrop.appendTo(tooltip);
+          return tooltip;
+        };
+        tooltipEl = renderTooltipEl();
+
+        // Destroy previously binded events
+        origin.off('mouseenter.tooltip mouseleave.tooltip');
+        // Mouse In
+        var started = false, timeoutRef;
+        origin.on({'mouseenter.tooltip': function(e) {
+          var showTooltip = function() {
+            setAttributes();
+            started = true;
+            tooltipEl.velocity('stop');
+            backdrop.velocity('stop');
+            tooltipEl.css({ visibility: 'visible', left: '0px', top: '0px' });
+
+            // Tooltip positioning
+            var originWidth = origin.outerWidth();
+            var originHeight = origin.outerHeight();
+            var tooltipHeight = tooltipEl.outerHeight();
+            var tooltipWidth = tooltipEl.outerWidth();
+            var tooltipVerticalMovement = '0px';
+            var tooltipHorizontalMovement = '0px';
+            var backdropOffsetWidth = backdrop[0].offsetWidth;
+            var backdropOffsetHeight = backdrop[0].offsetHeight;
+            var scaleXFactor = 8;
+            var scaleYFactor = 8;
+            var scaleFactor = 0;
+            var targetTop, targetLeft, newCoordinates;
+
+            if (tooltipPosition === "top") {
+              // Top Position
+              targetTop = origin.offset().top - tooltipHeight - margin;
+              targetLeft = origin.offset().left + originWidth/2 - tooltipWidth/2;
+              newCoordinates = repositionWithinScreen(targetLeft, targetTop, tooltipWidth, tooltipHeight);
+              tooltipVerticalMovement = '-10px';
+              backdrop.css({
+                bottom: 0,
+                left: 0,
+                borderRadius: '14px 14px 0 0',
+                transformOrigin: '50% 100%',
+                marginTop: tooltipHeight,
+                marginLeft: (tooltipWidth/2) - (backdropOffsetWidth/2)
+              });
+            }
+            // Left Position
+            else if (tooltipPosition === "left") {
+              targetTop = origin.offset().top + originHeight/2 - tooltipHeight/2;
+              targetLeft =  origin.offset().left - tooltipWidth - margin;
+              newCoordinates = repositionWithinScreen(targetLeft, targetTop, tooltipWidth, tooltipHeight);
+
+              tooltipHorizontalMovement = '-10px';
+              backdrop.css({
+                top: '-7px',
+                right: 0,
+                width: '14px',
+                height: '14px',
+                borderRadius: '14px 0 0 14px',
+                transformOrigin: '95% 50%',
+                marginTop: tooltipHeight/2,
+                marginLeft: tooltipWidth
+              });
+            }
+            // Right Position
+            else if (tooltipPosition === "right") {
+              targetTop = origin.offset().top + originHeight/2 - tooltipHeight/2;
+              targetLeft = origin.offset().left + originWidth + margin;
+              newCoordinates = repositionWithinScreen(targetLeft, targetTop, tooltipWidth, tooltipHeight);
+
+              tooltipHorizontalMovement = '+10px';
+              backdrop.css({
+                top: '-7px',
+                left: 0,
+                width: '14px',
+                height: '14px',
+                borderRadius: '0 14px 14px 0',
+                transformOrigin: '5% 50%',
+                marginTop: tooltipHeight/2,
+                marginLeft: '0px'
+              });
+            }
+            else {
+              // Bottom Position
+              targetTop = origin.offset().top + origin.outerHeight() + margin;
+              targetLeft = origin.offset().left + originWidth/2 - tooltipWidth/2;
+              newCoordinates = repositionWithinScreen(targetLeft, targetTop, tooltipWidth, tooltipHeight);
+              tooltipVerticalMovement = '+10px';
+              backdrop.css({
+                top: 0,
+                left: 0,
+                marginLeft: (tooltipWidth/2) - (backdropOffsetWidth/2)
+              });
+            }
+
+            // Set tooptip css placement
+            tooltipEl.css({
+              top: newCoordinates.y,
+              left: newCoordinates.x
+            });
+
+            // Calculate Scale to fill
+            scaleXFactor = Math.SQRT2 * tooltipWidth / parseInt(backdropOffsetWidth);
+            scaleYFactor = Math.SQRT2 * tooltipHeight / parseInt(backdropOffsetHeight);
+            scaleFactor = Math.max(scaleXFactor, scaleYFactor);
+
+            tooltipEl.velocity({ translateY: tooltipVerticalMovement, translateX: tooltipHorizontalMovement}, { duration: 350, queue: false })
+              .velocity({opacity: 1}, {duration: 300, delay: 50, queue: false});
+            backdrop.css({ visibility: 'visible' })
+              .velocity({opacity:1},{duration: 55, delay: 0, queue: false})
+              .velocity({scaleX: scaleFactor, scaleY: scaleFactor}, {duration: 300, delay: 0, queue: false, easing: 'easeInOutQuad'});
+          };
+
+          timeoutRef = setTimeout(showTooltip, tooltipDelay); // End Interval
+
+        // Mouse Out
+        },
+        'mouseleave.tooltip': function(){
+          // Reset State
+          started = false;
+          clearTimeout(timeoutRef);
+
+          // Animate back
+          setTimeout(function() {
+            if (started !== true) {
+              tooltipEl.velocity({
+                opacity: 0, translateY: 0, translateX: 0}, { duration: 225, queue: false});
+              backdrop.velocity({opacity: 0, scaleX: 1, scaleY: 1}, {
+                duration:225,
+                queue: false,
+                complete: function(){
+                  backdrop.css({ visibility: 'hidden' });
+                  tooltipEl.css({ visibility: 'hidden' });
+                  started = false;}
+              });
+            }
+          },225);
+        }
+        });
+    });
+  };
+
+  var repositionWithinScreen = function(x, y, width, height) {
+    var newX = x;
+    var newY = y;
+
+    if (newX < 0) {
+      newX = 4;
+    } else if (newX + width > window.innerWidth) {
+      newX -= newX + width - window.innerWidth;
+    }
+
+    if (newY < 0) {
+      newY = 4;
+    } else if (newY + height > window.innerHeight + $(window).scrollTop) {
+      newY -= newY + height - window.innerHeight;
+    }
+
+    return {x: newX, y: newY};
+  };
+
+  $(document).ready(function(){
+     $('.tooltipped').tooltip();
+   });
+}( jQuery ));
+;/*!
+ * Waves v0.6.4
+ * http://fian.my.id/Waves
+ *
+ * Copyright 2014 Alfiana E. Sibuea and other contributors
+ * Released under the MIT license
+ * https://github.com/fians/Waves/blob/master/LICENSE
+ */
+
+;(function(window) {
+    'use strict';
+
+    var Waves = Waves || {};
+    var $$ = document.querySelectorAll.bind(document);
+
+    // Find exact position of element
+    function isWindow(obj) {
+        return obj !== null && obj === obj.window;
+    }
+
+    function getWindow(elem) {
+        return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView;
+    }
+
+    function offset(elem) {
+        var docElem, win,
+            box = {top: 0, left: 0},
+            doc = elem && elem.ownerDocument;
+
+        docElem = doc.documentElement;
+
+        if (typeof elem.getBoundingClientRect !== typeof undefined) {
+            box = elem.getBoundingClientRect();
+        }
+        win = getWindow(doc);
+        return {
+            top: box.top + win.pageYOffset - docElem.clientTop,
+            left: box.left + win.pageXOffset - docElem.clientLeft
+        };
+    }
+
+    function convertStyle(obj) {
+        var style = '';
+
+        for (var a in obj) {
+            if (obj.hasOwnProperty(a)) {
+                style += (a + ':' + obj[a] + ';');
+            }
+        }
+
+        return style;
+    }
+
+    var Effect = {
+
+        // Effect delay
+        duration: 750,
+
+        show: function(e, element) {
+
+            // Disable right click
+            if (e.button === 2) {
+                return false;
+            }
+
+            var el = element || this;
+
+            // Create ripple
+            var ripple = document.createElement('div');
+            ripple.className = 'waves-ripple';
+            el.appendChild(ripple);
+
+            // Get click coordinate and element witdh
+            var pos         = offset(el);
+            var relativeY   = (e.pageY - pos.top);
+            var relativeX   = (e.pageX - pos.left);
+            var scale       = 'scale('+((el.clientWidth / 100) * 10)+')';
+
+            // Support for touch devices
+            if ('touches' in e) {
+              relativeY   = (e.touches[0].pageY - pos.top);
+              relativeX   = (e.touches[0].pageX - pos.left);
+            }
+
+            // Attach data to element
+            ripple.setAttribute('data-hold', Date.now());
+            ripple.setAttribute('data-scale', scale);
+            ripple.setAttribute('data-x', relativeX);
+            ripple.setAttribute('data-y', relativeY);
+
+            // Set ripple position
+            var rippleStyle = {
+                'top': relativeY+'px',
+                'left': relativeX+'px'
+            };
+
+            ripple.className = ripple.className + ' waves-notransition';
+            ripple.setAttribute('style', convertStyle(rippleStyle));
+            ripple.className = ripple.className.replace('waves-notransition', '');
+
+            // Scale the ripple
+            rippleStyle['-webkit-transform'] = scale;
+            rippleStyle['-moz-transform'] = scale;
+            rippleStyle['-ms-transform'] = scale;
+            rippleStyle['-o-transform'] = scale;
+            rippleStyle.transform = scale;
+            rippleStyle.opacity   = '1';
+
+            rippleStyle['-webkit-transition-duration'] = Effect.duration + 'ms';
+            rippleStyle['-moz-transition-duration']    = Effect.duration + 'ms';
+            rippleStyle['-o-transition-duration']      = Effect.duration + 'ms';
+            rippleStyle['transition-duration']         = Effect.duration + 'ms';
+
+            rippleStyle['-webkit-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)';
+            rippleStyle['-moz-transition-timing-function']    = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)';
+            rippleStyle['-o-transition-timing-function']      = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)';
+            rippleStyle['transition-timing-function']         = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)';
+
+            ripple.setAttribute('style', convertStyle(rippleStyle));
+        },
+
+        hide: function(e) {
+            TouchHandler.touchup(e);
+
+            var el = this;
+            var width = el.clientWidth * 1.4;
+
+            // Get first ripple
+            var ripple = null;
+            var ripples = el.getElementsByClassName('waves-ripple');
+            if (ripples.length > 0) {
+                ripple = ripples[ripples.length - 1];
+            } else {
+                return false;
+            }
+
+            var relativeX   = ripple.getAttribute('data-x');
+            var relativeY   = ripple.getAttribute('data-y');
+            var scale       = ripple.getAttribute('data-scale');
+
+            // Get delay beetween mousedown and mouse leave
+            var diff = Date.now() - Number(ripple.getAttribute('data-hold'));
+            var delay = 350 - diff;
+
+            if (delay < 0) {
+                delay = 0;
+            }
+
+            // Fade out ripple after delay
+            setTimeout(function() {
+                var style = {
+                    'top': relativeY+'px',
+                    'left': relativeX+'px',
+                    'opacity': '0',
+
+                    // Duration
+                    '-webkit-transition-duration': Effect.duration + 'ms',
+                    '-moz-transition-duration': Effect.duration + 'ms',
+                    '-o-transition-duration': Effect.duration + 'ms',
+                    'transition-duration': Effect.duration + 'ms',
+                    '-webkit-transform': scale,
+                    '-moz-transform': scale,
+                    '-ms-transform': scale,
+                    '-o-transform': scale,
+                    'transform': scale,
+                };
+
+                ripple.setAttribute('style', convertStyle(style));
+
+                setTimeout(function() {
+                    try {
+                        el.removeChild(ripple);
+                    } catch(e) {
+                        return false;
+                    }
+                }, Effect.duration);
+            }, delay);
+        },
+
+        // Little hack to make <input> can perform waves effect
+        wrapInput: function(elements) {
+            for (var a = 0; a < elements.length; a++) {
+                var el = elements[a];
+
+                if (el.tagName.toLowerCase() === 'input') {
+                    var parent = el.parentNode;
+
+                    // If input already have parent just pass through
+                    if (parent.tagName.toLowerCase() === 'i' && parent.className.indexOf('waves-effect') !== -1) {
+                        continue;
+                    }
+
+                    // Put element class and style to the specified parent
+                    var wrapper = document.createElement('i');
+                    wrapper.className = el.className + ' waves-input-wrapper';
+
+                    var elementStyle = el.getAttribute('style');
+
+                    if (!elementStyle) {
+                        elementStyle = '';
+                    }
+
+                    wrapper.setAttribute('style', elementStyle);
+
+                    el.className = 'waves-button-input';
+                    el.removeAttribute('style');
+
+                    // Put element as child
+                    parent.replaceChild(wrapper, el);
+                    wrapper.appendChild(el);
+                }
+            }
+        }
+    };
+
+
+    /**
+     * Disable mousedown event for 500ms during and after touch
+     */
+    var TouchHandler = {
+        /* uses an integer rather than bool so there's no issues with
+         * needing to clear timeouts if another touch event occurred
+         * within the 500ms. Cannot mouseup between touchstart and
+         * touchend, nor in the 500ms after touchend. */
+        touches: 0,
+        allowEvent: function(e) {
+            var allow = true;
+
+            if (e.type === 'touchstart') {
+                TouchHandler.touches += 1; //push
+            } else if (e.type === 'touchend' || e.type === 'touchcancel') {
+                setTimeout(function() {
+                    if (TouchHandler.touches > 0) {
+                        TouchHandler.touches -= 1; //pop after 500ms
+                    }
+                }, 500);
+            } else if (e.type === 'mousedown' && TouchHandler.touches > 0) {
+                allow = false;
+            }
+
+            return allow;
+        },
+        touchup: function(e) {
+            TouchHandler.allowEvent(e);
+        }
+    };
+
+
+    /**
+     * Delegated click handler for .waves-effect element.
+     * returns null when .waves-effect element not in "click tree"
+     */
+    function getWavesEffectElement(e) {
+        if (TouchHandler.allowEvent(e) === false) {
+            return null;
+        }
+
+        var element = null;
+        var target = e.target || e.srcElement;
+
+        while (target.parentElement !== null) {
+            if (!(target instanceof SVGElement) && target.className.indexOf('waves-effect') !== -1) {
+                element = target;
+                break;
+            } else if (target.classList.contains('waves-effect')) {
+                element = target;
+                break;
+            }
+            target = target.parentElement;
+        }
+
+        return element;
+    }
+
+    /**
+     * Bubble the click and show effect if .waves-effect elem was found
+     */
+    function showEffect(e) {
+        var element = getWavesEffectElement(e);
+
+        if (element !== null) {
+            Effect.show(e, element);
+
+            if ('ontouchstart' in window) {
+                element.addEventListener('touchend', Effect.hide, false);
+                element.addEventListener('touchcancel', Effect.hide, false);
+            }
+
+            element.addEventListener('mouseup', Effect.hide, false);
+            element.addEventListener('mouseleave', Effect.hide, false);
+        }
+    }
+
+    Waves.displayEffect = function(options) {
+        options = options || {};
+
+        if ('duration' in options) {
+            Effect.duration = options.duration;
+        }
+
+        //Wrap input inside <i> tag
+        Effect.wrapInput($$('.waves-effect'));
+
+        if ('ontouchstart' in window) {
+            document.body.addEventListener('touchstart', showEffect, false);
+        }
+
+        document.body.addEventListener('mousedown', showEffect, false);
+    };
+
+    /**
+     * Attach Waves to an input element (or any element which doesn't
+     * bubble mouseup/mousedown events).
+     *   Intended to be used with dynamically loaded forms/inputs, or
+     * where the user doesn't want a delegated click handler.
+     */
+    Waves.attach = function(element) {
+        //FUTURE: automatically add waves classes and allow users
+        // to specify them with an options param? Eg. light/classic/button
+        if (element.tagName.toLowerCase() === 'input') {
+            Effect.wrapInput([element]);
+            element = element.parentElement;
+        }
+
+        if ('ontouchstart' in window) {
+            element.addEventListener('touchstart', showEffect, false);
+        }
+
+        element.addEventListener('mousedown', showEffect, false);
+    };
+
+    window.Waves = Waves;
+
+    document.addEventListener('DOMContentLoaded', function() {
+        Waves.displayEffect();
+    }, false);
+
+})(window);
+;Materialize.toast = function (message, displayLength, className, completeCallback) {
+  className = className || "";
+
+  var container = document.getElementById('toast-container');
+
+  // Create toast container if it does not exist
+  if (container === null) {
+    // create notification container
+    container = document.createElement('div');
+    container.id = 'toast-container';
+    document.body.appendChild(container);
+  }
+
+  // Select and append toast
+  var newToast = createToast(message);
+
+  // only append toast if message is not undefined
+  if(message){
+    container.appendChild(newToast);
+  }
+
+  newToast.style.opacity = 0;
+
+  // Animate toast in
+  Vel(newToast, {translateY: '-35px',  opacity: 1 }, {duration: 300,
+    easing: 'easeOutCubic',
+    queue: false});
+
+  // Allows timer to be pause while being panned
+  var timeLeft = displayLength;
+  var counterInterval;
+  if (timeLeft != null)  {
+    counterInterval = setInterval (function(){
+      if (newToast.parentNode === null)
+        window.clearInterval(counterInterval);
+
+      // If toast is not being dragged, decrease its time remaining
+      if (!newToast.classList.contains('panning')) {
+        timeLeft -= 20;
+      }
+
+      if (timeLeft <= 0) {
+        // Animate toast out
+        Vel(newToast, {"opacity": 0, marginTop: '-40px'}, { duration: 375,
+            easing: 'easeOutExpo',
+            queue: false,
+            complete: function(){
+              // Call the optional callback
+              if(typeof(completeCallback) === "function")
+                completeCallback();
+              // Remove toast after it times out
+              this[0].parentNode.removeChild(this[0]);
+            }
+          });
+        window.clearInterval(counterInterval);
+      }
+    }, 20);
+  }
+
+
+
+  function createToast(html) {
+
+    // Create toast
+    var toast = document.createElement('div');
+    toast.classList.add('toast');
+    if (className) {
+      var classes = className.split(' ');
+
+      for (var i = 0, count = classes.length; i < count; i++) {
+        toast.classList.add(classes[i]);
+      }
+    }
+  // If type of parameter is HTML Element
+    if ( typeof HTMLElement === "object" ? html instanceof HTMLElement : html && typeof html === "object" && html !== null && html.nodeType === 1 && typeof html.nodeName==="string"
+) {
+      toast.appendChild(html);
+    }
+    else if (html instanceof jQuery) {
+      // Check if it is jQuery object
+      toast.appendChild(html[0]);
+    }
+    else {
+      // Insert as text;
+      toast.innerHTML = html;
+    }
+    // Bind hammer
+    var hammerHandler = new Hammer(toast, {prevent_default: false});
+    hammerHandler.on('pan', function(e) {
+      var deltaX = e.deltaX;
+      var activationDistance = 80;
+
+      // Change toast state
+      if (!toast.classList.contains('panning')){
+        toast.classList.add('panning');
+      }
+
+      var opacityPercent = 1-Math.abs(deltaX / activationDistance);
+      if (opacityPercent < 0)
+        opacityPercent = 0;
+
+      Vel(toast, {left: deltaX, opacity: opacityPercent }, {duration: 50, queue: false, easing: 'easeOutQuad'});
+
+    });
+
+    hammerHandler.on('panend', function(e) {
+      var deltaX = e.deltaX;
+      var activationDistance = 80;
+
+      // If toast dragged past activation point
+      if (Math.abs(deltaX) > activationDistance) {
+        Vel(toast, {marginTop: '-40px'}, { duration: 375,
+          easing: 'easeOutExpo',
+          queue: false,
+          complete: function(){
+            if(typeof(completeCallback) === "function") {
+              completeCallback();
+            }
+            toast.parentNode.removeChild(toast);
+          }
+        });
+
+      } else {
+        toast.classList.remove('panning');
+        // Put toast back into original position
+        Vel(toast, { left: 0, opacity: 1 }, { duration: 300,
+          easing: 'easeOutExpo',
+          queue: false
+        });
+
+      }
+    });
+
+    return toast;
+  }
+};
+;(function ($) {
+
+  var methods = {
+    init : function(options) {
+      var defaults = {
+        menuWidth: 300,
+        edge: 'left',
+        closeOnClick: false,
+        draggable: true
+      };
+      options = $.extend(defaults, options);
+
+      $(this).each(function(){
+        var $this = $(this);
+        var menuId = $this.attr('data-activates');
+        var menu = $("#"+ menuId);
+
+        // Set to width
+        if (options.menuWidth != 300) {
+          menu.css('width', options.menuWidth);
+        }
+
+        // Add Touch Area
+        var $dragTarget = $('.drag-target[data-sidenav="' + menuId + '"]');
+        if (options.draggable) {
+          // Regenerate dragTarget
+          if ($dragTarget.length) {
+            $dragTarget.remove();
+          }
+
+          $dragTarget = $('<div class="drag-target"></div>').attr('data-sidenav', menuId);
+          $('body').append($dragTarget);
+        } else {
+          $dragTarget = $();
+        }
+
+        if (options.edge == 'left') {
+          menu.css('transform', 'translateX(-100%)');
+          $dragTarget.css({'left': 0}); // Add Touch Area
+        }
+        else {
+          menu.addClass('right-aligned') // Change text-alignment to right
+            .css('transform', 'translateX(100%)');
+          $dragTarget.css({'right': 0}); // Add Touch Area
+        }
+
+        // If fixed sidenav, bring menu out
+        if (menu.hasClass('fixed')) {
+            if (window.innerWidth > 992) {
+              menu.css('transform', 'translateX(0)');
+            }
+          }
+
+        // Window resize to reset on large screens fixed
+        if (menu.hasClass('fixed')) {
+          $(window).resize( function() {
+            if (window.innerWidth > 992) {
+              // Close menu if window is resized bigger than 992 and user has fixed sidenav
+              if ($('#sidenav-overlay').length !== 0 && menuOut) {
+                removeMenu(true);
+              }
+              else {
+                // menu.removeAttr('style');
+                menu.css('transform', 'translateX(0%)');
+                // menu.css('width', options.menuWidth);
+              }
+            }
+            else if (menuOut === false){
+              if (options.edge === 'left') {
+                menu.css('transform', 'translateX(-100%)');
+              } else {
+                menu.css('transform', 'translateX(100%)');
+              }
+
+            }
+
+          });
+        }
+
+        // if closeOnClick, then add close event for all a tags in side sideNav
+        if (options.closeOnClick === true) {
+          menu.on("click.itemclick", "a:not(.collapsible-header)", function(){
+            removeMenu();
+          });
+        }
+
+        var removeMenu = function(restoreNav) {
+          panning = false;
+          menuOut = false;
+          // Reenable scrolling
+          $('body').css({
+            overflow: '',
+            width: ''
+          });
+
+          $('#sidenav-overlay').velocity({opacity: 0}, {duration: 200,
+              queue: false, easing: 'easeOutQuad',
+            complete: function() {
+              $(this).remove();
+            } });
+          if (options.edge === 'left') {
+            // Reset phantom div
+            $dragTarget.css({width: '', right: '', left: '0'});
+            menu.velocity(
+              {'translateX': '-100%'},
+              { duration: 200,
+                queue: false,
+                easing: 'easeOutCubic',
+                complete: function() {
+                  if (restoreNav === true) {
+                    // Restore Fixed sidenav
+                    menu.removeAttr('style');
+                    menu.css('width', options.menuWidth);
+                  }
+                }
+
+            });
+          }
+          else {
+            // Reset phantom div
+            $dragTarget.css({width: '', right: '0', left: ''});
+            menu.velocity(
+              {'translateX': '100%'},
+              { duration: 200,
+                queue: false,
+                easing: 'easeOutCubic',
+                complete: function() {
+                  if (restoreNav === true) {
+                    // Restore Fixed sidenav
+                    menu.removeAttr('style');
+                    menu.css('width', options.menuWidth);
+                  }
+                }
+              });
+          }
+        };
+
+
+
+        // Touch Event
+        var panning = false;
+        var menuOut = false;
+
+        if (options.draggable) {
+          $dragTarget.on('click', function(){
+            if (menuOut) {
+              removeMenu();
+            }
+          });
+
+          $dragTarget.hammer({
+            prevent_default: false
+          }).bind('pan', function(e) {
+
+            if (e.gesture.pointerType == "touch") {
+
+              var direction = e.gesture.direction;
+              var x = e.gesture.center.x;
+              var y = e.gesture.center.y;
+              var velocityX = e.gesture.velocityX;
+
+              // Disable Scrolling
+              var $body = $('body');
+              var $overlay = $('#sidenav-overlay');
+              var oldWidth = $body.innerWidth();
+              $body.css('overflow', 'hidden');
+              $body.width(oldWidth);
+
+              // If overlay does not exist, create one and if it is clicked, close menu
+              if ($overlay.length === 0) {
+                $overlay = $('<div id="sidenav-overlay"></div>');
+                $overlay.css('opacity', 0).click( function(){
+                  removeMenu();
+                });
+                $('body').append($overlay);
+              }
+
+              // Keep within boundaries
+              if (options.edge === 'left') {
+                if (x > options.menuWidth) { x = options.menuWidth; }
+                else if (x < 0) { x = 0; }
+              }
+
+              if (options.edge === 'left') {
+                // Left Direction
+                if (x < (options.menuWidth / 2)) { menuOut = false; }
+                // Right Direction
+                else if (x >= (options.menuWidth / 2)) { menuOut = true; }
+                menu.css('transform', 'translateX(' + (x - options.menuWidth) + 'px)');
+              }
+              else {
+                // Left Direction
+                if (x < (window.innerWidth - options.menuWidth / 2)) {
+                  menuOut = true;
+                }
+                // Right Direction
+                else if (x >= (window.innerWidth - options.menuWidth / 2)) {
+                 menuOut = false;
+               }
+                var rightPos = (x - options.menuWidth / 2);
+                if (rightPos < 0) {
+                  rightPos = 0;
+                }
+
+                menu.css('transform', 'translateX(' + rightPos + 'px)');
+              }
+
+
+              // Percentage overlay
+              var overlayPerc;
+              if (options.edge === 'left') {
+                overlayPerc = x / options.menuWidth;
+                $overlay.velocity({opacity: overlayPerc }, {duration: 10, queue: false, easing: 'easeOutQuad'});
+              }
+              else {
+                overlayPerc = Math.abs((x - window.innerWidth) / options.menuWidth);
+                $overlay.velocity({opacity: overlayPerc }, {duration: 10, queue: false, easing: 'easeOutQuad'});
+              }
+            }
+
+          }).bind('panend', function(e) {
+
+            if (e.gesture.pointerType == "touch") {
+              var $overlay = $('<div id="sidenav-overlay"></div>');
+              var velocityX = e.gesture.velocityX;
+              var x = e.gesture.center.x;
+              var leftPos = x - options.menuWidth;
+              var rightPos = x - options.menuWidth / 2;
+              if (leftPos > 0 ) {
+                leftPos = 0;
+              }
+              if (rightPos < 0) {
+                rightPos = 0;
+              }
+              panning = false;
+
+              if (options.edge === 'left') {
+                // If velocityX <= 0.3 then the user is flinging the menu closed so ignore menuOut
+                if ((menuOut && velocityX <= 0.3) || velocityX < -0.5) {
+                  // Return menu to open
+                  if (leftPos !== 0) {
+                    menu.velocity({'translateX': [0, leftPos]}, {duration: 300, queue: false, easing: 'easeOutQuad'});
+                  }
+
+                  $overlay.velocity({opacity: 1 }, {duration: 50, queue: false, easing: 'easeOutQuad'});
+                  $dragTarget.css({width: '50%', right: 0, left: ''});
+                  menuOut = true;
+                }
+                else if (!menuOut || velocityX > 0.3) {
+                  // Enable Scrolling
+                  $('body').css({
+                    overflow: '',
+                    width: ''
+                  });
+                  // Slide menu closed
+                  menu.velocity({'translateX': [-1 * options.menuWidth - 10, leftPos]}, {duration: 200, queue: false, easing: 'easeOutQuad'});
+                  $overlay.velocity({opacity: 0 }, {duration: 200, queue: false, easing: 'easeOutQuad',
+                    complete: function () {
+                      $(this).remove();
+                    }});
+                  $dragTarget.css({width: '10px', right: '', left: 0});
+                }
+              }
+              else {
+                if ((menuOut && velocityX >= -0.3) || velocityX > 0.5) {
+                  // Return menu to open
+                  if (rightPos !== 0) {
+                    menu.velocity({'translateX': [0, rightPos]}, {duration: 300, queue: false, easing: 'easeOutQuad'});
+                  }
+
+                  $overlay.velocity({opacity: 1 }, {duration: 50, queue: false, easing: 'easeOutQuad'});
+                  $dragTarget.css({width: '50%', right: '', left: 0});
+                  menuOut = true;
+                }
+                else if (!menuOut || velocityX < -0.3) {
+                  // Enable Scrolling
+                  $('body').css({
+                    overflow: '',
+                    width: ''
+                  });
+
+                  // Slide menu closed
+                  menu.velocity({'translateX': [options.menuWidth + 10, rightPos]}, {duration: 200, queue: false, easing: 'easeOutQuad'});
+                  $overlay.velocity({opacity: 0 }, {duration: 200, queue: false, easing: 'easeOutQuad',
+                    complete: function () {
+                      $(this).remove();
+                    }});
+                  $dragTarget.css({width: '10px', right: 0, left: ''});
+                }
+              }
+
+            }
+          });
+        }
+
+        $this.off('click.sidenav').on('click.sidenav', function() {
+          if (menuOut === true) {
+            menuOut = false;
+            panning = false;
+            removeMenu();
+          }
+          else {
+
+            // Disable Scrolling
+            var $body = $('body');
+            var $overlay = $('<div id="sidenav-overlay"></div>');
+            var oldWidth = $body.innerWidth();
+            $body.css('overflow', 'hidden');
+            $body.width(oldWidth);
+
+            // Push current drag target on top of DOM tree
+            $('body').append($dragTarget);
+
+            if (options.edge === 'left') {
+              $dragTarget.css({width: '50%', right: 0, left: ''});
+              menu.velocity({'translateX': [0, -1 * options.menuWidth]}, {duration: 300, queue: false, easing: 'easeOutQuad'});
+            }
+            else {
+              $dragTarget.css({width: '50%', right: '', left: 0});
+              menu.velocity({'translateX': [0, options.menuWidth]}, {duration: 300, queue: false, easing: 'easeOutQuad'});
+            }
+
+            $overlay.css('opacity', 0)
+            .click(function(){
+              menuOut = false;
+              panning = false;
+              removeMenu();
+              $overlay.velocity({opacity: 0}, {duration: 300, queue: false, easing: 'easeOutQuad',
+                complete: function() {
+                  $(this).remove();
+                } });
+
+            });
+            $('body').append($overlay);
+            $overlay.velocity({opacity: 1}, {duration: 300, queue: false, easing: 'easeOutQuad',
+              complete: function () {
+                menuOut = true;
+                panning = false;
+              }
+            });
+          }
+
+          return false;
+        });
+      });
+
+
+    },
+    destroy: function () {
+      var $overlay = $('#sidenav-overlay');
+      var $dragTarget = $('.drag-target[data-sidenav="' + $(this).attr('data-activates') + '"]');
+      $overlay.trigger('click');
+      $dragTarget.remove();
+      $(this).off('click');
+      $overlay.remove();
+    },
+    show : function() {
+      this.trigger('click');
+    },
+    hide : function() {
+      $('#sidenav-overlay').trigger('click');
+    }
+  };
+
+
+  $.fn.sideNav = function(methodOrOptions) {
+    if ( methods[methodOrOptions] ) {
+      return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+      // Default to "init"
+      return methods.init.apply( this, arguments );
+    } else {
+      $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.sideNav' );
+    }
+  }; // Plugin end
+}( jQuery ));
+;/**
+ * Extend jquery with a scrollspy plugin.
+ * This watches the window scroll and fires events when elements are scrolled into viewport.
+ *
+ * throttle() and getTime() taken from Underscore.js
+ * https://github.com/jashkenas/underscore
+ *
+ * @author Copyright 2013 John Smart
+ * @license https://raw.github.com/thesmart/jquery-scrollspy/master/LICENSE
+ * @see https://github.com/thesmart
+ * @version 0.1.2
+ */
+(function($) {
+
+       var jWindow = $(window);
+       var elements = [];
+       var elementsInView = [];
+       var isSpying = false;
+       var ticks = 0;
+       var unique_id = 1;
+       var offset = {
+               top : 0,
+               right : 0,
+               bottom : 0,
+               left : 0,
+       }
+
+       /**
+        * Find elements that are within the boundary
+        * @param {number} top
+        * @param {number} right
+        * @param {number} bottom
+        * @param {number} left
+        * @return {jQuery}             A collection of elements
+        */
+       function findElements(top, right, bottom, left) {
+               var hits = $();
+               $.each(elements, function(i, element) {
+                       if (element.height() > 0) {
+                               var elTop = element.offset().top,
+                                       elLeft = element.offset().left,
+                                       elRight = elLeft + element.width(),
+                                       elBottom = elTop + element.height();
+
+                               var isIntersect = !(elLeft > right ||
+                                       elRight < left ||
+                                       elTop > bottom ||
+                                       elBottom < top);
+
+                               if (isIntersect) {
+                                       hits.push(element);
+                               }
+                       }
+               });
+
+               return hits;
+       }
+
+
+       /**
+        * Called when the user scrolls the window
+        */
+       function onScroll(scrollOffset) {
+               // unique tick id
+               ++ticks;
+
+               // viewport rectangle
+               var top = jWindow.scrollTop(),
+                       left = jWindow.scrollLeft(),
+                       right = left + jWindow.width(),
+                       bottom = top + jWindow.height();
+
+               // determine which elements are in view
+               var intersections = findElements(top+offset.top + scrollOffset || 200, right+offset.right, bottom+offset.bottom, left+offset.left);
+               $.each(intersections, function(i, element) {
+
+                       var lastTick = element.data('scrollSpy:ticks');
+                       if (typeof lastTick != 'number') {
+                               // entered into view
+                               element.triggerHandler('scrollSpy:enter');
+                       }
+
+                       // update tick id
+                       element.data('scrollSpy:ticks', ticks);
+               });
+
+               // determine which elements are no longer in view
+               $.each(elementsInView, function(i, element) {
+                       var lastTick = element.data('scrollSpy:ticks');
+                       if (typeof lastTick == 'number' && lastTick !== ticks) {
+                               // exited from view
+                               element.triggerHandler('scrollSpy:exit');
+                               element.data('scrollSpy:ticks', null);
+                       }
+               });
+
+               // remember elements in view for next tick
+               elementsInView = intersections;
+       }
+
+       /**
+        * Called when window is resized
+       */
+       function onWinSize() {
+               jWindow.trigger('scrollSpy:winSize');
+       }
+
+
+       /**
+        * Enables ScrollSpy using a selector
+        * @param {jQuery|string} selector  The elements collection, or a selector
+        * @param {Object=} options     Optional.
+        throttle : number -> scrollspy throttling. Default: 100 ms
+        offsetTop : number -> offset from top. Default: 0
+        offsetRight : number -> offset from right. Default: 0
+        offsetBottom : number -> offset from bottom. Default: 0
+        offsetLeft : number -> offset from left. Default: 0
+        * @returns {jQuery}
+        */
+       $.scrollSpy = function(selector, options) {
+         var defaults = {
+                       throttle: 100,
+                       scrollOffset: 200 // offset - 200 allows elements near bottom of page to scroll
+    };
+    options = $.extend(defaults, options);
+
+               var visible = [];
+               selector = $(selector);
+               selector.each(function(i, element) {
+                       elements.push($(element));
+                       $(element).data("scrollSpy:id", i);
+                       // Smooth scroll to section
+                 $('a[href="#' + $(element).attr('id') + '"]').click(function(e) {
+                   e.preventDefault();
+                   var offset = $(Materialize.escapeHash(this.hash)).offset().top + 1;
+               $('html, body').animate({ scrollTop: offset - options.scrollOffset }, {duration: 400, queue: false, easing: 'easeOutCubic'});
+                 });
+               });
+
+               offset.top = options.offsetTop || 0;
+               offset.right = options.offsetRight || 0;
+               offset.bottom = options.offsetBottom || 0;
+               offset.left = options.offsetLeft || 0;
+
+               var throttledScroll = Materialize.throttle(function() {
+                       onScroll(options.scrollOffset);
+               }, options.throttle || 100);
+               var readyScroll = function(){
+                       $(document).ready(throttledScroll);
+               };
+
+               if (!isSpying) {
+                       jWindow.on('scroll', readyScroll);
+                       jWindow.on('resize', readyScroll);
+                       isSpying = true;
+               }
+
+               // perform a scan once, after current execution context, and after dom is ready
+               setTimeout(readyScroll, 0);
+
+
+               selector.on('scrollSpy:enter', function() {
+                       visible = $.grep(visible, function(value) {
+             return value.height() != 0;
+           });
+
+                       var $this = $(this);
+
+                       if (visible[0]) {
+                               $('a[href="#' + visible[0].attr('id') + '"]').removeClass('active');
+                               if ($this.data('scrollSpy:id') < visible[0].data('scrollSpy:id')) {
+                                       visible.unshift($(this));
+                               }
+                               else {
+                                       visible.push($(this));
+                               }
+                       }
+                       else {
+                               visible.push($(this));
+                       }
+
+
+                       $('a[href="#' + visible[0].attr('id') + '"]').addClass('active');
+               });
+               selector.on('scrollSpy:exit', function() {
+                       visible = $.grep(visible, function(value) {
+             return value.height() != 0;
+           });
+
+                       if (visible[0]) {
+                               $('a[href="#' + visible[0].attr('id') + '"]').removeClass('active');
+                               var $this = $(this);
+                               visible = $.grep(visible, function(value) {
+               return value.attr('id') != $this.attr('id');
+             });
+             if (visible[0]) { // Check if empty
+                                       $('a[href="#' + visible[0].attr('id') + '"]').addClass('active');
+             }
+                       }
+               });
+
+               return selector;
+       };
+
+       /**
+        * Listen for window resize events
+        * @param {Object=} options                                             Optional. Set { throttle: number } to change throttling. Default: 100 ms
+        * @returns {jQuery}            $(window)
+        */
+       $.winSizeSpy = function(options) {
+               $.winSizeSpy = function() { return jWindow; }; // lock from multiple calls
+               options = options || {
+                       throttle: 100
+               };
+               return jWindow.on('resize', Materialize.throttle(onWinSize, options.throttle || 100));
+       };
+
+       /**
+        * Enables ScrollSpy on a collection of elements
+        * e.g. $('.scrollSpy').scrollSpy()
+        * @param {Object=} options     Optional.
+                                                                                       throttle : number -> scrollspy throttling. Default: 100 ms
+                                                                                       offsetTop : number -> offset from top. Default: 0
+                                                                                       offsetRight : number -> offset from right. Default: 0
+                                                                                       offsetBottom : number -> offset from bottom. Default: 0
+                                                                                       offsetLeft : number -> offset from left. Default: 0
+        * @returns {jQuery}
+        */
+       $.fn.scrollSpy = function(options) {
+               return $.scrollSpy($(this), options);
+       };
+
+})(jQuery);
+;(function ($) {
+  $(document).ready(function() {
+
+    // Function to update labels of text fields
+    Materialize.updateTextFields = function() {
+      var input_selector = 'input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search], textarea';
+      $(input_selector).each(function(index, element) {
+        var $this = $(this);
+        if ($(element).val().length > 0 || element.autofocus || $this.attr('placeholder') !== undefined) {
+          $this.siblings('label').addClass('active');
+        } else if ($(element)[0].validity) {
+          $this.siblings('label').toggleClass('active', $(element)[0].validity.badInput === true);
+        } else {
+          $this.siblings('label').removeClass('active');
+        }
+      });
+    };
+
+    // Text based inputs
+    var input_selector = 'input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search], textarea';
+
+    // Add active if form auto complete
+    $(document).on('change', input_selector, function () {
+      if($(this).val().length !== 0 || $(this).attr('placeholder') !== undefined) {
+        $(this).siblings('label').addClass('active');
+      }
+      validate_field($(this));
+    });
+
+    // Add active if input element has been pre-populated on document ready
+    $(document).ready(function() {
+      Materialize.updateTextFields();
+    });
+
+    // HTML DOM FORM RESET handling
+    $(document).on('reset', function(e) {
+      var formReset = $(e.target);
+      if (formReset.is('form')) {
+        formReset.find(input_selector).removeClass('valid').removeClass('invalid');
+        formReset.find(input_selector).each(function () {
+          if ($(this).attr('value') === '') {
+            $(this).siblings('label').removeClass('active');
+          }
+        });
+
+        // Reset select
+        formReset.find('select.initialized').each(function () {
+          var reset_text = formReset.find('option[selected]').text();
+          formReset.siblings('input.select-dropdown').val(reset_text);
+        });
+      }
+    });
+
+    // Add active when element has focus
+    $(document).on('focus', input_selector, function () {
+      $(this).siblings('label, .prefix').addClass('active');
+    });
+
+    $(document).on('blur', input_selector, function () {
+      var $inputElement = $(this);
+      var selector = ".prefix";
+
+      if ($inputElement.val().length === 0 && $inputElement[0].validity.badInput !== true && $inputElement.attr('placeholder') === undefined) {
+        selector += ", label";
+      }
+
+      $inputElement.siblings(selector).removeClass('active');
+
+      validate_field($inputElement);
+    });
+
+    window.validate_field = function(object) {
+      var hasLength = object.attr('data-length') !== undefined;
+      var lenAttr = parseInt(object.attr('data-length'));
+      var len = object.val().length;
+
+      if (object.val().length === 0 && object[0].validity.badInput === false) {
+        if (object.hasClass('validate')) {
+          object.removeClass('valid');
+          object.removeClass('invalid');
+        }
+      }
+      else {
+        if (object.hasClass('validate')) {
+          // Check for character counter attributes
+          if ((object.is(':valid') && hasLength && (len <= lenAttr)) || (object.is(':valid') && !hasLength)) {
+            object.removeClass('invalid');
+            object.addClass('valid');
+          }
+          else {
+            object.removeClass('valid');
+            object.addClass('invalid');
+          }
+        }
+      }
+    };
+
+    // Radio and Checkbox focus class
+    var radio_checkbox = 'input[type=radio], input[type=checkbox]';
+    $(document).on('keyup.radio', radio_checkbox, function(e) {
+      // TAB, check if tabbing to radio or checkbox.
+      if (e.which === 9) {
+        $(this).addClass('tabbed');
+        var $this = $(this);
+        $this.one('blur', function(e) {
+
+          $(this).removeClass('tabbed');
+        });
+        return;
+      }
+    });
+
+    // Textarea Auto Resize
+    var hiddenDiv = $('.hiddendiv').first();
+    if (!hiddenDiv.length) {
+      hiddenDiv = $('<div class="hiddendiv common"></div>');
+      $('body').append(hiddenDiv);
+    }
+    var text_area_selector = '.materialize-textarea';
+
+    function textareaAutoResize($textarea) {
+      // Set font properties of hiddenDiv
+
+      var fontFamily = $textarea.css('font-family');
+      var fontSize = $textarea.css('font-size');
+      var lineHeight = $textarea.css('line-height');
+
+      if (fontSize) { hiddenDiv.css('font-size', fontSize); }
+      if (fontFamily) { hiddenDiv.css('font-family', fontFamily); }
+      if (lineHeight) { hiddenDiv.css('line-height', lineHeight); }
+
+      if ($textarea.attr('wrap') === "off") {
+        hiddenDiv.css('overflow-wrap', "normal")
+                 .css('white-space', "pre");
+      }
+
+      hiddenDiv.text($textarea.val() + '\n');
+      var content = hiddenDiv.html().replace(/\n/g, '<br>');
+      hiddenDiv.html(content);
+
+
+      // When textarea is hidden, width goes crazy.
+      // Approximate with half of window size
+
+      if ($textarea.is(':visible')) {
+        hiddenDiv.css('width', $textarea.width());
+      }
+      else {
+        hiddenDiv.css('width', $(window).width()/2);
+      }
+
+      $textarea.css('height', hiddenDiv.height());
+    }
+
+    $(text_area_selector).each(function () {
+      var $textarea = $(this);
+      if ($textarea.val().length) {
+        textareaAutoResize($textarea);
+      }
+    });
+
+    $('body').on('keyup keydown autoresize', text_area_selector, function () {
+      textareaAutoResize($(this));
+    });
+
+    // File Input Path
+    $(document).on('change', '.file-field input[type="file"]', function () {
+      var file_field = $(this).closest('.file-field');
+      var path_input = file_field.find('input.file-path');
+      var files      = $(this)[0].files;
+      var file_names = [];
+      for (var i = 0; i < files.length; i++) {
+        file_names.push(files[i].name);
+      }
+      path_input.val(file_names.join(", "));
+      path_input.trigger('change');
+    });
+
+    /****************
+    *  Range Input  *
+    ****************/
+
+    var range_type = 'input[type=range]';
+    var range_mousedown = false;
+    var left;
+
+    $(range_type).each(function () {
+      var thumb = $('<span class="thumb"><span class="value"></span></span>');
+      $(this).after(thumb);
+    });
+
+    var range_wrapper = '.range-field';
+    $(document).on('change', range_type, function(e) {
+      var thumb = $(this).siblings('.thumb');
+      thumb.find('.value').html($(this).val());
+    });
+
+    $(document).on('input mousedown touchstart', range_type, function(e) {
+      var thumb = $(this).siblings('.thumb');
+      var width = $(this).outerWidth();
+
+      // If thumb indicator does not exist yet, create it
+      if (thumb.length <= 0) {
+        thumb = $('<span class="thumb"><span class="value"></span></span>');
+        $(this).after(thumb);
+      }
+
+      // Set indicator value
+      thumb.find('.value').html($(this).val());
+
+      range_mousedown = true;
+      $(this).addClass('active');
+
+      if (!thumb.hasClass('active')) {
+        thumb.velocity({ height: "30px", width: "30px", top: "-20px", marginLeft: "-15px"}, { duration: 300, easing: 'easeOutExpo' });
+      }
+
+      if (e.type !== 'input') {
+        if(e.pageX === undefined || e.pageX === null){//mobile
+           left = e.originalEvent.touches[0].pageX - $(this).offset().left;
+        }
+        else{ // desktop
+           left = e.pageX - $(this).offset().left;
+        }
+        if (left < 0) {
+          left = 0;
+        }
+        else if (left > width) {
+          left = width;
+        }
+        thumb.addClass('active').css('left', left);
+      }
+
+      thumb.find('.value').html($(this).val());
+    });
+
+    $(document).on('mouseup touchend', range_wrapper, function() {
+      range_mousedown = false;
+      $(this).removeClass('active');
+    });
+
+    $(document).on('mousemove touchmove', range_wrapper, function(e) {
+      var thumb = $(this).children('.thumb');
+      var left;
+      if (range_mousedown) {
+        if (!thumb.hasClass('active')) {
+          thumb.velocity({ height: '30px', width: '30px', top: '-20px', marginLeft: '-15px'}, { duration: 300, easing: 'easeOutExpo' });
+        }
+        if (e.pageX === undefined || e.pageX === null) { //mobile
+          left = e.originalEvent.touches[0].pageX - $(this).offset().left;
+        }
+        else{ // desktop
+          left = e.pageX - $(this).offset().left;
+        }
+        var width = $(this).outerWidth();
+
+        if (left < 0) {
+          left = 0;
+        }
+        else if (left > width) {
+          left = width;
+        }
+        thumb.addClass('active').css('left', left);
+        thumb.find('.value').html(thumb.siblings(range_type).val());
+      }
+    });
+
+    $(document).on('mouseout touchleave', range_wrapper, function() {
+      if (!range_mousedown) {
+
+        var thumb = $(this).children('.thumb');
+
+        if (thumb.hasClass('active')) {
+          thumb.velocity({ height: '0', width: '0', top: '10px', marginLeft: '-6px'}, { duration: 100 });
+        }
+        thumb.removeClass('active');
+      }
+    });
+
+    /**************************
+     * Auto complete plugin  *
+     *************************/
+    $.fn.autocomplete = function (options) {
+      // Defaults
+      var defaults = {
+        data: {},
+        limit: Infinity,
+        onAutocomplete: null
+      };
+
+      options = $.extend(defaults, options);
+
+      return this.each(function() {
+        var $input = $(this);
+        var data = options.data,
+            count = 0,
+            activeIndex = 0,
+            oldVal,
+            $inputDiv = $input.closest('.input-field'); // Div to append on
+
+        // Check if data isn't empty
+        if (!$.isEmptyObject(data)) {
+          var $autocomplete = $('<ul class="autocomplete-content dropdown-content"></ul>');
+          var $oldAutocomplete;
+
+          // Append autocomplete element.
+          // Prevent double structure init.
+          if ($inputDiv.length) {
+            $oldAutocomplete = $inputDiv.children('.autocomplete-content.dropdown-content').first();
+            if (!$oldAutocomplete.length) {
+              $inputDiv.append($autocomplete); // Set ul in body
+            }
+          } else {
+            $oldAutocomplete = $input.next('.autocomplete-content.dropdown-content');
+            if (!$oldAutocomplete.length) {
+              $input.after($autocomplete);
+            }
+          }
+          if ($oldAutocomplete.length) {
+            $autocomplete = $oldAutocomplete;
+          }
+
+          // Highlight partial match.
+          var highlight = function(string, $el) {
+            var img = $el.find('img');
+            var matchStart = $el.text().toLowerCase().indexOf("" + string.toLowerCase() + ""),
+                matchEnd = matchStart + string.length - 1,
+                beforeMatch = $el.text().slice(0, matchStart),
+                matchText = $el.text().slice(matchStart, matchEnd + 1),
+                afterMatch = $el.text().slice(matchEnd + 1);
+            $el.html("<span>" + beforeMatch + "<span class='highlight'>" + matchText + "</span>" + afterMatch + "</span>");
+            if (img.length) {
+              $el.prepend(img);
+            }
+          };
+
+          // Reset current element position
+          var resetCurrentElement = function() {
+            activeIndex = 0;
+            $autocomplete.find('.active').removeClass('active');
+          }
+
+          // Perform search
+          $input.off('keyup.autocomplete').on('keyup.autocomplete', function (e) {
+            // Reset count.
+            count = 0;
+
+            // Don't capture enter or arrow key usage.
+            if (e.which === 13 ||
+                e.which === 38 ||
+                e.which === 40) {
+              return;
+            }
+
+            var val = $input.val().toLowerCase();
+
+            // Check if the input isn't empty
+            if (oldVal !== val) {
+              $autocomplete.empty();
+              resetCurrentElement();
+
+              if (val !== '') {
+                for(var key in data) {
+                  if (data.hasOwnProperty(key) &&
+                      key.toLowerCase().indexOf(val) !== -1 &&
+                      key.toLowerCase() !== val) {
+                    // Break if past limit
+                    if (count >= options.limit) {
+                      break;
+                    }
+
+                    var autocompleteOption = $('<li></li>');
+                    if (!!data[key]) {
+                      autocompleteOption.append('<img src="'+ data[key] +'" class="right circle"><span>'+ key +'</span>');
+                    } else {
+                      autocompleteOption.append('<span>'+ key +'</span>');
+                    }
+
+                    $autocomplete.append(autocompleteOption);
+                    highlight(val, autocompleteOption);
+                    count++;
+                  }
+                }
+              }
+            }
+
+            // Update oldVal
+            oldVal = val;
+          });
+
+          $input.off('keydown.autocomplete').on('keydown.autocomplete', function (e) {
+            // Arrow keys and enter key usage
+            var keyCode = e.which,
+                liElement,
+                numItems = $autocomplete.children('li').length,
+                $active = $autocomplete.children('.active').first();
+
+            // select element on Enter
+            if (keyCode === 13) {
+              liElement = $autocomplete.children('li').eq(activeIndex);
+              if (liElement.length) {
+                liElement.click();
+                e.preventDefault();
+              }
+              return;
+            }
+
+            // Capture up and down key
+            if ( keyCode === 38 || keyCode === 40 ) {
+              e.preventDefault();
+
+              if (keyCode === 38 &&
+                  activeIndex > 0) {
+                activeIndex--;
+              }
+
+              if (keyCode === 40 &&
+                  activeIndex < (numItems - 1) &&
+                  $active.length) {
+                activeIndex++;
+              }
+
+              $active.removeClass('active');
+              $autocomplete.children('li').eq(activeIndex).addClass('active');
+            }
+          });
+
+          // Set input value
+          $autocomplete.on('click', 'li', function () {
+            var text = $(this).text().trim();
+            $input.val(text);
+            $input.trigger('change');
+            $autocomplete.empty();
+            resetCurrentElement();
+
+            // Handle onAutocomplete callback.
+            if (typeof(options.onAutocomplete) === "function") {
+              options.onAutocomplete.call(this, text);
+            }
+          });
+        }
+      });
+    };
+
+  }); // End of $(document).ready
+
+  /*******************
+   *  Select Plugin  *
+   ******************/
+  $.fn.material_select = function (callback) {
+    $(this).each(function(){
+      var $select = $(this);
+
+      if ($select.hasClass('browser-default')) {
+        return; // Continue to next (return false breaks out of entire loop)
+      }
+
+      var multiple = $select.attr('multiple') ? true : false,
+          lastID = $select.data('select-id'); // Tear down structure if Select needs to be rebuilt
+
+      if (lastID) {
+        $select.parent().find('span.caret').remove();
+        $select.parent().find('input').remove();
+
+        $select.unwrap();
+        $('ul#select-options-'+lastID).remove();
+      }
+
+      // If destroying the select, remove the selelct-id and reset it to it's uninitialized state.
+      if(callback === 'destroy') {
+        $select.data('select-id', null).removeClass('initialized');
+        return;
+      }
+
+      var uniqueID = Materialize.guid();
+      $select.data('select-id', uniqueID);
+      var wrapper = $('<div class="select-wrapper"></div>');
+      wrapper.addClass($select.attr('class'));
+      var options = $('<ul id="select-options-' + uniqueID +'" class="dropdown-content select-dropdown ' + (multiple ? 'multiple-select-dropdown' : '') + '"></ul>'),
+          selectChildren = $select.children('option, optgroup'),
+          valuesSelected = [],
+          optionsHover = false;
+
+      var label = $select.find('option:selected').html() || $select.find('option:first').html() || "";
+
+      // Function that renders and appends the option taking into
+      // account type and possible image icon.
+      var appendOptionWithIcon = function(select, option, type) {
+        // Add disabled attr if disabled
+        var disabledClass = (option.is(':disabled')) ? 'disabled ' : '';
+        var optgroupClass = (type === 'optgroup-option') ? 'optgroup-option ' : '';
+
+        // add icons
+        var icon_url = option.data('icon');
+        var classes = option.attr('class');
+        if (!!icon_url) {
+          var classString = '';
+          if (!!classes) classString = ' class="' + classes + '"';
+
+          // Check for multiple type.
+          if (type === 'multiple') {
+            options.append($('<li class="' + disabledClass + '"><img alt="" src="' + icon_url + '"' + classString + '><span><input type="checkbox"' + disabledClass + '/><label></label>' + option.html() + '</span></li>'));
+          } else {
+            options.append($('<li class="' + disabledClass + optgroupClass + '"><img alt="" src="' + icon_url + '"' + classString + '><span>' + option.html() + '</span></li>'));
+          }
+          return true;
+        }
+
+        // Check for multiple type.
+        if (type === 'multiple') {
+          options.append($('<li class="' + disabledClass + '"><span><input type="checkbox"' + disabledClass + '/><label></label>' + option.html() + '</span></li>'));
+        } else {
+          options.append($('<li class="' + disabledClass + optgroupClass + '"><span>' + option.html() + '</span></li>'));
+        }
+      };
+
+      /* Create dropdown structure. */
+      if (selectChildren.length) {
+        selectChildren.each(function() {
+          if ($(this).is('option')) {
+            // Direct descendant option.
+            if (multiple) {
+              appendOptionWithIcon($select, $(this), 'multiple');
+
+            } else {
+              appendOptionWithIcon($select, $(this));
+            }
+          } else if ($(this).is('optgroup')) {
+            // Optgroup.
+            var selectOptions = $(this).children('option');
+            options.append($('<li class="optgroup"><span>' + $(this).attr('label') + '</span></li>'));
+
+            selectOptions.each(function() {
+              appendOptionWithIcon($select, $(this), 'optgroup-option');
+            });
+          }
+        });
+      }
+
+      options.find('li:not(.optgroup)').each(function (i) {
+        $(this).click(function (e) {
+          // Check if option element is disabled
+          if (!$(this).hasClass('disabled') && !$(this).hasClass('optgroup')) {
+            var selected = true;
+
+            if (multiple) {
+              $('input[type="checkbox"]', this).prop('checked', function(i, v) { return !v; });
+              selected = toggleEntryFromArray(valuesSelected, $(this).index(), $select);
+              $newSelect.trigger('focus');
+            } else {
+              options.find('li').removeClass('active');
+              $(this).toggleClass('active');
+              $newSelect.val($(this).text());
+            }
+
+            activateOption(options, $(this));
+            $select.find('option').eq(i).prop('selected', selected);
+            // Trigger onchange() event
+            $select.trigger('change');
+            if (typeof callback !== 'undefined') callback();
+          }
+
+          e.stopPropagation();
+        });
+      });
+
+      // Wrap Elements
+      $select.wrap(wrapper);
+      // Add Select Display Element
+      var dropdownIcon = $('<span class="caret">&#9660;</span>');
+      if ($select.is(':disabled'))
+        dropdownIcon.addClass('disabled');
+
+      // escape double quotes
+      var sanitizedLabelHtml = label.replace(/"/g, '&quot;');
+
+      var $newSelect = $('<input type="text" class="select-dropdown" readonly="true" ' + (($select.is(':disabled')) ? 'disabled' : '') + ' data-activates="select-options-' + uniqueID +'" value="'+ sanitizedLabelHtml +'"/>');
+      $select.before($newSelect);
+      $newSelect.before(dropdownIcon);
+
+      $newSelect.after(options);
+      // Check if section element is disabled
+      if (!$select.is(':disabled')) {
+        $newSelect.dropdown({'hover': false, 'closeOnClick': false});
+      }
+
+      // Copy tabindex
+      if ($select.attr('tabindex')) {
+        $($newSelect[0]).attr('tabindex', $select.attr('tabindex'));
+      }
+
+      $select.addClass('initialized');
+
+      $newSelect.on({
+        'focus': function (){
+          if ($('ul.select-dropdown').not(options[0]).is(':visible')) {
+            $('input.select-dropdown').trigger('close');
+          }
+          if (!options.is(':visible')) {
+            $(this).trigger('open', ['focus']);
+            var label = $(this).val();
+            if (multiple && label.indexOf(',') >= 0) {
+              label = label.split(',')[0];
+            }
+
+            var selectedOption = options.find('li').filter(function() {
+              return $(this).text().toLowerCase() === label.toLowerCase();
+            })[0];
+            activateOption(options, selectedOption, true);
+          }
+        },
+        'click': function (e){
+          e.stopPropagation();
+        }
+      });
+
+      $newSelect.on('blur', function() {
+        if (!multiple) {
+          $(this).trigger('close');
+        }
+        options.find('li.selected').removeClass('selected');
+      });
+
+      options.hover(function() {
+        optionsHover = true;
+      }, function () {
+        optionsHover = false;
+      });
+
+      $(window).on({
+        'click': function () {
+          multiple && (optionsHover || $newSelect.trigger('close'));
+        }
+      });
+
+      // Add initial multiple selections.
+      if (multiple) {
+        $select.find("option:selected:not(:disabled)").each(function () {
+          var index = $(this).index();
+
+          toggleEntryFromArray(valuesSelected, index, $select);
+          options.find("li").eq(index).find(":checkbox").prop("checked", true);
+        });
+      }
+
+      /**
+       * Make option as selected and scroll to selected position
+       * @param {jQuery} collection  Select options jQuery element
+       * @param {Element} newOption  element of the new option
+       * @param {Boolean} firstActivation  If on first activation of select
+       */
+      var activateOption = function(collection, newOption, firstActivation) {
+        if (newOption) {
+          collection.find('li.selected').removeClass('selected');
+          var option = $(newOption);
+          option.addClass('selected');
+          if (!multiple || !!firstActivation) {
+            options.scrollTo(option);
+          }
+        }
+      };
+
+      // Allow user to search by typing
+      // this array is cleared after 1 second
+      var filterQuery = [],
+          onKeyDown = function(e){
+            // TAB - switch to another input
+            if(e.which == 9){
+              $newSelect.trigger('close');
+              return;
+            }
+
+            // ARROW DOWN WHEN SELECT IS CLOSED - open select options
+            if(e.which == 40 && !options.is(':visible')){
+              $newSelect.trigger('open');
+              return;
+            }
+
+            // ENTER WHEN SELECT IS CLOSED - submit form
+            if(e.which == 13 && !options.is(':visible')){
+              return;
+            }
+
+            e.preventDefault();
+
+            // CASE WHEN USER TYPE LETTERS
+            var letter = String.fromCharCode(e.which).toLowerCase(),
+                nonLetters = [9,13,27,38,40];
+            if (letter && (nonLetters.indexOf(e.which) === -1)) {
+              filterQuery.push(letter);
+
+              var string = filterQuery.join(''),
+                  newOption = options.find('li').filter(function() {
+                    return $(this).text().toLowerCase().indexOf(string) === 0;
+                  })[0];
+
+              if (newOption) {
+                activateOption(options, newOption);
+              }
+            }
+
+            // ENTER - select option and close when select options are opened
+            if (e.which == 13) {
+              var activeOption = options.find('li.selected:not(.disabled)')[0];
+              if(activeOption){
+                $(activeOption).trigger('click');
+                if (!multiple) {
+                  $newSelect.trigger('close');
+                }
+              }
+            }
+
+            // ARROW DOWN - move to next not disabled option
+            if (e.which == 40) {
+              if (options.find('li.selected').length) {
+                newOption = options.find('li.selected').next('li:not(.disabled)')[0];
+              } else {
+                newOption = options.find('li:not(.disabled)')[0];
+              }
+              activateOption(options, newOption);
+            }
+
+            // ESC - close options
+            if (e.which == 27) {
+              $newSelect.trigger('close');
+            }
+
+            // ARROW UP - move to previous not disabled option
+            if (e.which == 38) {
+              newOption = options.find('li.selected').prev('li:not(.disabled)')[0];
+              if(newOption)
+                activateOption(options, newOption);
+            }
+
+            // Automaticaly clean filter query so user can search again by starting letters
+            setTimeout(function(){ filterQuery = []; }, 1000);
+          };
+
+      $newSelect.on('keydown', onKeyDown);
+    });
+
+    function toggleEntryFromArray(entriesArray, entryIndex, select) {
+      var index = entriesArray.indexOf(entryIndex),
+          notAdded = index === -1;
+
+      if (notAdded) {
+        entriesArray.push(entryIndex);
+      } else {
+        entriesArray.splice(index, 1);
+      }
+
+      select.siblings('ul.dropdown-content').find('li').eq(entryIndex).toggleClass('active');
+
+      // use notAdded instead of true (to detect if the option is selected or not)
+      select.find('option').eq(entryIndex).prop('selected', notAdded);
+      setValueToInput(entriesArray, select);
+
+      return notAdded;
+    }
+
+    function setValueToInput(entriesArray, select) {
+      var value = '';
+
+      for (var i = 0, count = entriesArray.length; i < count; i++) {
+        var text = select.find('option').eq(entriesArray[i]).text();
+
+        i === 0 ? value += text : value += ', ' + text;
+      }
+
+      if (value === '') {
+        value = select.find('option:disabled').eq(0).text();
+      }
+
+      select.siblings('input.select-dropdown').val(value);
+    }
+  };
+
+}( jQuery ));
+;(function ($) {
+
+  var methods = {
+
+    init : function(options) {
+      var defaults = {
+        indicators: true,
+        height: 400,
+        transition: 500,
+        interval: 6000
+      };
+      options = $.extend(defaults, options);
+
+      return this.each(function() {
+
+        // For each slider, we want to keep track of
+        // which slide is active and its associated content
+        var $this = $(this);
+        var $slider = $this.find('ul.slides').first();
+        var $slides = $slider.find('> li');
+        var $active_index = $slider.find('.active').index();
+        var $active, $indicators, $interval;
+        if ($active_index != -1) { $active = $slides.eq($active_index); }
+
+        // Transitions the caption depending on alignment
+        function captionTransition(caption, duration) {
+          if (caption.hasClass("center-align")) {
+            caption.velocity({opacity: 0, translateY: -100}, {duration: duration, queue: false});
+          }
+          else if (caption.hasClass("right-align")) {
+            caption.velocity({opacity: 0, translateX: 100}, {duration: duration, queue: false});
+          }
+          else if (caption.hasClass("left-align")) {
+            caption.velocity({opacity: 0, translateX: -100}, {duration: duration, queue: false});
+          }
+        }
+
+        // This function will transition the slide to any index of the next slide
+        function moveToSlide(index) {
+          // Wrap around indices.
+          if (index >= $slides.length) index = 0;
+          else if (index < 0) index = $slides.length -1;
+
+          $active_index = $slider.find('.active').index();
+
+          // Only do if index changes
+          if ($active_index != index) {
+            $active = $slides.eq($active_index);
+            $caption = $active.find('.caption');
+
+            $active.removeClass('active');
+            $active.velocity({opacity: 0}, {duration: options.transition, queue: false, easing: 'easeOutQuad',
+                              complete: function() {
+                                $slides.not('.active').velocity({opacity: 0, translateX: 0, translateY: 0}, {duration: 0, queue: false});
+                              } });
+            captionTransition($caption, options.transition);
+
+
+            // Update indicators
+            if (options.indicators) {
+              $indicators.eq($active_index).removeClass('active');
+            }
+
+            $slides.eq(index).velocity({opacity: 1}, {duration: options.transition, queue: false, easing: 'easeOutQuad'});
+            $slides.eq(index).find('.caption').velocity({opacity: 1, translateX: 0, translateY: 0}, {duration: options.transition, delay: options.transition, queue: false, easing: 'easeOutQuad'});
+            $slides.eq(index).addClass('active');
+
+
+            // Update indicators
+            if (options.indicators) {
+              $indicators.eq(index).addClass('active');
+            }
+          }
+        }
+
+        // Set height of slider
+        // If fullscreen, do nothing
+        if (!$this.hasClass('fullscreen')) {
+          if (options.indicators) {
+            // Add height if indicators are present
+            $this.height(options.height + 40);
+          }
+          else {
+            $this.height(options.height);
+          }
+          $slider.height(options.height);
+        }
+
+
+        // Set initial positions of captions
+        $slides.find('.caption').each(function () {
+          captionTransition($(this), 0);
+        });
+
+        // Move img src into background-image
+        $slides.find('img').each(function () {
+          var placeholderBase64 = 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+          if ($(this).attr('src') !== placeholderBase64) {
+            $(this).css('background-image', 'url(' + $(this).attr('src') + ')' );
+            $(this).attr('src', placeholderBase64);
+          }
+        });
+
+        // dynamically add indicators
+        if (options.indicators) {
+          $indicators = $('<ul class="indicators"></ul>');
+          $slides.each(function( index ) {
+            var $indicator = $('<li class="indicator-item"></li>');
+
+            // Handle clicks on indicators
+            $indicator.click(function () {
+              var $parent = $slider.parent();
+              var curr_index = $parent.find($(this)).index();
+              moveToSlide(curr_index);
+
+              // reset interval
+              clearInterval($interval);
+              $interval = setInterval(
+                function(){
+                  $active_index = $slider.find('.active').index();
+                  if ($slides.length == $active_index + 1) $active_index = 0; // loop to start
+                  else $active_index += 1;
+
+                  moveToSlide($active_index);
+
+                }, options.transition + options.interval
+              );
+            });
+            $indicators.append($indicator);
+          });
+          $this.append($indicators);
+          $indicators = $this.find('ul.indicators').find('li.indicator-item');
+        }
+
+        if ($active) {
+          $active.show();
+        }
+        else {
+          $slides.first().addClass('active').velocity({opacity: 1}, {duration: options.transition, queue: false, easing: 'easeOutQuad'});
+
+          $active_index = 0;
+          $active = $slides.eq($active_index);
+
+          // Update indicators
+          if (options.indicators) {
+            $indicators.eq($active_index).addClass('active');
+          }
+        }
+
+        // Adjust height to current slide
+        $active.find('img').each(function() {
+          $active.find('.caption').velocity({opacity: 1, translateX: 0, translateY: 0}, {duration: options.transition, queue: false, easing: 'easeOutQuad'});
+        });
+
+        // auto scroll
+        $interval = setInterval(
+          function(){
+            $active_index = $slider.find('.active').index();
+            moveToSlide($active_index + 1);
+
+          }, options.transition + options.interval
+        );
+
+
+        // HammerJS, Swipe navigation
+
+        // Touch Event
+        var panning = false;
+        var swipeLeft = false;
+        var swipeRight = false;
+
+        $this.hammer({
+            prevent_default: false
+        }).bind('pan', function(e) {
+          if (e.gesture.pointerType === "touch") {
+
+            // reset interval
+            clearInterval($interval);
+
+            var direction = e.gesture.direction;
+            var x = e.gesture.deltaX;
+            var velocityX = e.gesture.velocityX;
+            var velocityY = e.gesture.velocityY;
+
+            $curr_slide = $slider.find('.active');
+            if (Math.abs(velocityX) > Math.abs(velocityY)) {
+              $curr_slide.velocity({ translateX: x
+                  }, {duration: 50, queue: false, easing: 'easeOutQuad'});
+            }
+
+            // Swipe Left
+            if (direction === 4 && (x > ($this.innerWidth() / 2) || velocityX < -0.65)) {
+              swipeRight = true;
+            }
+            // Swipe Right
+            else if (direction === 2 && (x < (-1 * $this.innerWidth() / 2) || velocityX > 0.65)) {
+              swipeLeft = true;
+            }
+
+            // Make Slide Behind active slide visible
+            var next_slide;
+            if (swipeLeft) {
+              next_slide = $curr_slide.next();
+              if (next_slide.length === 0) {
+                next_slide = $slides.first();
+              }
+              next_slide.velocity({ opacity: 1
+                  }, {duration: 300, queue: false, easing: 'easeOutQuad'});
+            }
+            if (swipeRight) {
+              next_slide = $curr_slide.prev();
+              if (next_slide.length === 0) {
+                next_slide = $slides.last();
+              }
+              next_slide.velocity({ opacity: 1
+                  }, {duration: 300, queue: false, easing: 'easeOutQuad'});
+            }
+
+
+          }
+
+        }).bind('panend', function(e) {
+          if (e.gesture.pointerType === "touch") {
+
+            $curr_slide = $slider.find('.active');
+            panning = false;
+            curr_index = $slider.find('.active').index();
+
+            if (!swipeRight && !swipeLeft || $slides.length <=1) {
+              // Return to original spot
+              $curr_slide.velocity({ translateX: 0
+                  }, {duration: 300, queue: false, easing: 'easeOutQuad'});
+            }
+            else if (swipeLeft) {
+              moveToSlide(curr_index + 1);
+              $curr_slide.velocity({translateX: -1 * $this.innerWidth() }, {duration: 300, queue: false, easing: 'easeOutQuad',
+                                    complete: function() {
+                                      $curr_slide.velocity({opacity: 0, translateX: 0}, {duration: 0, queue: false});
+                                    } });
+            }
+            else if (swipeRight) {
+              moveToSlide(curr_index - 1);
+              $curr_slide.velocity({translateX: $this.innerWidth() }, {duration: 300, queue: false, easing: 'easeOutQuad',
+                                    complete: function() {
+                                      $curr_slide.velocity({opacity: 0, translateX: 0}, {duration: 0, queue: false});
+                                    } });
+            }
+            swipeLeft = false;
+            swipeRight = false;
+
+            // Restart interval
+            clearInterval($interval);
+            $interval = setInterval(
+              function(){
+                $active_index = $slider.find('.active').index();
+                if ($slides.length == $active_index + 1) $active_index = 0; // loop to start
+                else $active_index += 1;
+
+                moveToSlide($active_index);
+
+              }, options.transition + options.interval
+            );
+          }
+        });
+
+        $this.on('sliderPause', function() {
+          clearInterval($interval);
+        });
+
+        $this.on('sliderStart', function() {
+          clearInterval($interval);
+          $interval = setInterval(
+            function(){
+              $active_index = $slider.find('.active').index();
+              if ($slides.length == $active_index + 1) $active_index = 0; // loop to start
+              else $active_index += 1;
+
+              moveToSlide($active_index);
+
+            }, options.transition + options.interval
+          );
+        });
+
+        $this.on('sliderNext', function() {
+          $active_index = $slider.find('.active').index();
+          moveToSlide($active_index + 1);
+        });
+
+        $this.on('sliderPrev', function() {
+          $active_index = $slider.find('.active').index();
+          moveToSlide($active_index - 1);
+        });
+
+      });
+
+
+
+    },
+    pause : function() {
+      $(this).trigger('sliderPause');
+    },
+    start : function() {
+      $(this).trigger('sliderStart');
+    },
+    next : function() {
+      $(this).trigger('sliderNext');
+    },
+    prev : function() {
+      $(this).trigger('sliderPrev');
+    }
+  };
+
+
+  $.fn.slider = function(methodOrOptions) {
+    if ( methods[methodOrOptions] ) {
+      return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+      // Default to "init"
+      return methods.init.apply( this, arguments );
+    } else {
+      $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.tooltip' );
+    }
+  }; // Plugin end
+}( jQuery ));
+;(function ($) {
+  $(document).ready(function() {
+
+    $(document).on('click.card', '.card', function (e) {
+      if ($(this).find('> .card-reveal').length) {
+        if ($(e.target).is($('.card-reveal .card-title')) || $(e.target).is($('.card-reveal .card-title i'))) {
+          // Make Reveal animate down and display none
+          $(this).find('.card-reveal').velocity(
+            {translateY: 0}, {
+              duration: 225,
+              queue: false,
+              easing: 'easeInOutQuad',
+              complete: function() { $(this).css({ display: 'none'}); }
+            }
+          );
+        }
+        else if ($(e.target).is($('.card .activator')) ||
+                 $(e.target).is($('.card .activator i')) ) {
+          $(e.target).closest('.card').css('overflow', 'hidden');
+          $(this).find('.card-reveal').css({ display: 'block'}).velocity("stop", false).velocity({translateY: '-100%'}, {duration: 300, queue: false, easing: 'easeInOutQuad'});
+        }
+      }
+    });
+
+  });
+}( jQuery ));;(function ($) {
+  var materialChipsDefaults = {
+    data: [],
+    placeholder: '',
+    secondaryPlaceholder: '',
+    autocompleteData: {},
+    autocompleteLimit: Infinity,
+  };
+
+  $(document).ready(function() {
+    // Handle removal of static chips.
+    $(document).on('click', '.chip .close', function(e){
+      var $chips = $(this).closest('.chips');
+      if ($chips.attr('data-initialized')) {
+        return;
+      }
+      $(this).closest('.chip').remove();
+    });
+  });
+
+  $.fn.material_chip = function (options) {
+    var self = this;
+    this.$el = $(this);
+    this.$document = $(document);
+    this.SELS = {
+      CHIPS: '.chips',
+      CHIP: '.chip',
+      INPUT: 'input',
+      DELETE: '.material-icons',
+      SELECTED_CHIP: '.selected',
+    };
+
+    if ('data' === options) {
+      return this.$el.data('chips');
+    }
+
+    var curr_options = $.extend({}, materialChipsDefaults, options);
+    self.hasAutocomplete = !$.isEmptyObject(curr_options.autocompleteData);
+
+    // Initialize
+    this.init = function() {
+      var i = 0;
+      var chips;
+      self.$el.each(function(){
+        var $chips = $(this);
+        var chipId = Materialize.guid();
+        self.chipId = chipId;
+
+        if (!curr_options.data || !(curr_options.data instanceof Array)) {
+          curr_options.data = [];
+        }
+        $chips.data('chips', curr_options.data);
+        $chips.attr('data-index', i);
+        $chips.attr('data-initialized', true);
+
+        if (!$chips.hasClass(self.SELS.CHIPS)) {
+          $chips.addClass('chips');
+        }
+
+        self.chips($chips, chipId);
+        i++;
+      });
+    };
+
+    this.handleEvents = function() {
+      var SELS = self.SELS;
+
+      self.$document.off('click.chips-focus', SELS.CHIPS).on('click.chips-focus', SELS.CHIPS, function(e){
+        $(e.target).find(SELS.INPUT).focus();
+      });
+
+      self.$document.off('click.chips-select', SELS.CHIP).on('click.chips-select', SELS.CHIP, function(e){
+        var $chip = $(e.target);
+        if ($chip.length) {
+          var wasSelected = $chip.hasClass('selected');
+          var $chips = $chip.closest(SELS.CHIPS);
+          $(SELS.CHIP).removeClass('selected');
+
+          if (!wasSelected) {
+            self.selectChip($chip.index(), $chips);
+          }
+        }
+      });
+
+      self.$document.off('keydown.chips').on('keydown.chips', function(e){
+        if ($(e.target).is('input, textarea')) {
+          return;
+        }
+
+        // delete
+        var $chip = self.$document.find(SELS.CHIP + SELS.SELECTED_CHIP);
+        var $chips = $chip.closest(SELS.CHIPS);
+        var length = $chip.siblings(SELS.CHIP).length;
+        var index;
+
+        if (!$chip.length) {
+          return;
+        }
+
+        if (e.which === 8 || e.which === 46) {
+          e.preventDefault();
+
+          index = $chip.index();
+          self.deleteChip(index, $chips);
+
+          var selectIndex = null;
+          if ((index + 1) < length) {
+            selectIndex = index;
+          } else if (index === length || (index + 1) === length) {
+            selectIndex = length - 1;
+          }
+
+          if (selectIndex < 0) selectIndex = null;
+
+          if (null !== selectIndex) {
+            self.selectChip(selectIndex, $chips);
+          }
+          if (!length) $chips.find('input').focus();
+
+        // left
+        } else if (e.which === 37) {
+          index = $chip.index() - 1;
+          if (index < 0) {
+            return;
+          }
+          $(SELS.CHIP).removeClass('selected');
+          self.selectChip(index, $chips);
+
+        // right
+        } else if (e.which === 39) {
+          index = $chip.index() + 1;
+          $(SELS.CHIP).removeClass('selected');
+          if (index > length) {
+            $chips.find('input').focus();
+            return;
+          }
+          self.selectChip(index, $chips);
+        }
+      });
+
+      self.$document.off('focusin.chips', SELS.CHIPS + ' ' + SELS.INPUT).on('focusin.chips', SELS.CHIPS + ' ' + SELS.INPUT, function(e){
+        var $currChips = $(e.target).closest(SELS.CHIPS);
+        $currChips.addClass('focus');
+        $currChips.siblings('label, .prefix').addClass('active');
+        $(SELS.CHIP).removeClass('selected');
+      });
+
+      self.$document.off('focusout.chips', SELS.CHIPS + ' ' + SELS.INPUT).on('focusout.chips', SELS.CHIPS + ' ' + SELS.INPUT, function(e){
+        var $currChips = $(e.target).closest(SELS.CHIPS);
+        $currChips.removeClass('focus');
+
+        // Remove active if empty
+        if (!$currChips.data('chips').length) {
+          $currChips.siblings('label').removeClass('active');
+        }
+        $currChips.siblings('.prefix').removeClass('active');
+      });
+
+      self.$document.off('keydown.chips-add', SELS.CHIPS + ' ' + SELS.INPUT).on('keydown.chips-add', SELS.CHIPS + ' ' + SELS.INPUT, function(e){
+        var $target = $(e.target);
+        var $chips = $target.closest(SELS.CHIPS);
+        var chipsLength = $chips.children(SELS.CHIP).length;
+
+        // enter
+        if (13 === e.which) {
+          // Override enter if autocompleting.
+          if (self.hasAutocomplete &&
+              $chips.find('.autocomplete-content.dropdown-content').length &&
+              $chips.find('.autocomplete-content.dropdown-content').children().length) {
+            return;
+          }
+
+          e.preventDefault();
+          self.addChip({tag: $target.val()}, $chips);
+          $target.val('');
+          return;
+        }
+
+        // delete or left
+        if ((8 === e.keyCode || 37 === e.keyCode) && '' === $target.val() && chipsLength) {
+          e.preventDefault();
+          self.selectChip(chipsLength - 1, $chips);
+          $target.blur();
+          return;
+        }
+      });
+
+      // Click on delete icon in chip.
+      self.$document.off('click.chips-delete', SELS.CHIPS + ' ' + SELS.DELETE).on('click.chips-delete', SELS.CHIPS + ' ' + SELS.DELETE, function(e) {
+        var $target = $(e.target);
+        var $chips = $target.closest(SELS.CHIPS);
+        var $chip = $target.closest(SELS.CHIP);
+        e.stopPropagation();
+        self.deleteChip($chip.index(), $chips);
+        $chips.find('input').focus();
+      });
+    };
+
+    this.chips = function($chips, chipId) {
+      var html = '';
+      $chips.data('chips').forEach(function(elem){
+        html += self.renderChip(elem);
+      });
+      html += '<input id="' + chipId +'" class="input" placeholder="">';
+      $chips.html(html);
+      self.setPlaceholder($chips);
+
+      // Set for attribute for label
+      var label = $chips.next('label');
+      if (label.length) {
+        label.attr('for', chipId);
+
+        if ($chips.data('chips').length) {
+          label.addClass('active');
+        }
+      }
+
+      // Setup autocomplete if needed.
+      var input = $('#' + chipId);
+      if (self.hasAutocomplete) {
+        input.autocomplete({
+          data: curr_options.autocompleteData,
+          limit: curr_options.autocompleteLimit,
+          onAutocomplete: function(val) {
+            self.addChip({tag: val}, $chips);
+            input.val('');
+            input.focus();
+          },
+        })
+      }
+    };
+
+    this.renderChip = function(elem) {
+      if (!elem.tag) return;
+
+      var html = '<div class="chip">' + elem.tag;
+      if (elem.image) {
+        html += ' <img src="' + elem.image + '"> ';
+      }
+      html += '<i class="material-icons close">close</i>';
+      html += '</div>';
+      return html;
+    };
+
+    this.setPlaceholder = function($chips) {
+      if ($chips.data('chips').length && curr_options.placeholder) {
+        $chips.find('input').prop('placeholder', curr_options.placeholder);
+
+      } else if (!$chips.data('chips').length && curr_options.secondaryPlaceholder) {
+        $chips.find('input').prop('placeholder', curr_options.secondaryPlaceholder);
+      }
+    };
+
+    this.isValid = function($chips, elem) {
+      var chips = $chips.data('chips');
+      var exists = false;
+      for (var i=0; i < chips.length; i++) {
+        if (chips[i].tag === elem.tag) {
+            exists = true;
+            return;
+        }
+      }
+      return '' !== elem.tag && !exists;
+    };
+
+    this.addChip = function(elem, $chips) {
+      if (!self.isValid($chips, elem)) {
+        return;
+      }
+      var chipHtml = self.renderChip(elem);
+      var newData = [];
+      var oldData = $chips.data('chips');
+      for (var i = 0; i < oldData.length; i++) {
+        newData.push(oldData[i]);
+      }
+      newData.push(elem);
+
+      $chips.data('chips', newData);
+      $(chipHtml).insertBefore($chips.find('input'));
+      $chips.trigger('chip.add', elem);
+      self.setPlaceholder($chips);
+    };
+
+    this.deleteChip = function(chipIndex, $chips) {
+      var chip = $chips.data('chips')[chipIndex];
+      $chips.find('.chip').eq(chipIndex).remove();
+
+      var newData = [];
+      var oldData = $chips.data('chips');
+      for (var i = 0; i < oldData.length; i++) {
+        if (i !== chipIndex) {
+          newData.push(oldData[i]);
+        }
+      }
+
+      $chips.data('chips', newData);
+      $chips.trigger('chip.delete', chip);
+      self.setPlaceholder($chips);
+    };
+
+    this.selectChip = function(chipIndex, $chips) {
+      var $chip = $chips.find('.chip').eq(chipIndex);
+      if ($chip && false === $chip.hasClass('selected')) {
+        $chip.addClass('selected');
+        $chips.trigger('chip.select', $chips.data('chips')[chipIndex]);
+      }
+    };
+
+    this.getChipsElement = function(index, $chips) {
+      return $chips.eq(index);
+    };
+
+    // init
+    this.init();
+
+    this.handleEvents();
+  };
+}( jQuery ));
+;(function ($) {
+  $.fn.pushpin = function (options) {
+    // Defaults
+    var defaults = {
+      top: 0,
+      bottom: Infinity,
+      offset: 0
+    };
+
+    // Remove pushpin event and classes
+    if (options === "remove") {
+      this.each(function () {
+        if (id = $(this).data('pushpin-id')) {
+          $(window).off('scroll.' + id);
+          $(this).removeData('pushpin-id').removeClass('pin-top pinned pin-bottom').removeAttr('style');
+        }
+      });
+      return false;
+    }
+
+    options = $.extend(defaults, options);
+
+
+    $index = 0;
+    return this.each(function() {
+      var $uniqueId = Materialize.guid(),
+          $this = $(this),
+          $original_offset = $(this).offset().top;
+
+      function removePinClasses(object) {
+        object.removeClass('pin-top');
+        object.removeClass('pinned');
+        object.removeClass('pin-bottom');
+      }
+
+      function updateElements(objects, scrolled) {
+        objects.each(function () {
+          // Add position fixed (because its between top and bottom)
+          if (options.top <= scrolled && options.bottom >= scrolled && !$(this).hasClass('pinned')) {
+            removePinClasses($(this));
+            $(this).css('top', options.offset);
+            $(this).addClass('pinned');
+          }
+
+          // Add pin-top (when scrolled position is above top)
+          if (scrolled < options.top && !$(this).hasClass('pin-top')) {
+            removePinClasses($(this));
+            $(this).css('top', 0);
+            $(this).addClass('pin-top');
+          }
+
+          // Add pin-bottom (when scrolled position is below bottom)
+          if (scrolled > options.bottom && !$(this).hasClass('pin-bottom')) {
+            removePinClasses($(this));
+            $(this).addClass('pin-bottom');
+            $(this).css('top', options.bottom - $original_offset);
+          }
+        });
+      }
+
+      $(this).data('pushpin-id', $uniqueId);
+      updateElements($this, $(window).scrollTop());
+      $(window).on('scroll.' + $uniqueId, function () {
+        var $scrolled = $(window).scrollTop() + options.offset;
+        updateElements($this, $scrolled);
+      });
+
+    });
+
+  };
+}( jQuery ));;(function ($) {
+  $(document).ready(function() {
+
+    // jQuery reverse
+    $.fn.reverse = [].reverse;
+
+    // Hover behaviour: make sure this doesn't work on .click-to-toggle FABs!
+    $(document).on('mouseenter.fixedActionBtn', '.fixed-action-btn:not(.click-to-toggle):not(.toolbar)', function(e) {
+      var $this = $(this);
+      openFABMenu($this);
+    });
+    $(document).on('mouseleave.fixedActionBtn', '.fixed-action-btn:not(.click-to-toggle):not(.toolbar)', function(e) {
+      var $this = $(this);
+      closeFABMenu($this);
+    });
+
+    // Toggle-on-click behaviour.
+    $(document).on('click.fabClickToggle', '.fixed-action-btn.click-to-toggle > a', function(e) {
+      var $this = $(this);
+      var $menu = $this.parent();
+      if ($menu.hasClass('active')) {
+        closeFABMenu($menu);
+      } else {
+        openFABMenu($menu);
+      }
+    });
+
+    // Toolbar transition behaviour.
+    $(document).on('click.fabToolbar', '.fixed-action-btn.toolbar > a', function(e) {
+      var $this = $(this);
+      var $menu = $this.parent();
+      FABtoToolbar($menu);
+    });
+
+  });
+
+  $.fn.extend({
+    openFAB: function() {
+      openFABMenu($(this));
+    },
+    closeFAB: function() {
+      closeFABMenu($(this));
+    },
+    openToolbar: function() {
+      FABtoToolbar($(this));
+    },
+    closeToolbar: function() {
+      toolbarToFAB($(this));
+    }
+  });
+
+
+  var openFABMenu = function (btn) {
+    var $this = btn;
+    if ($this.hasClass('active') === false) {
+
+      // Get direction option
+      var horizontal = $this.hasClass('horizontal');
+      var offsetY, offsetX;
+
+      if (horizontal === true) {
+        offsetX = 40;
+      } else {
+        offsetY = 40;
+      }
+
+      $this.addClass('active');
+      $this.find('ul .btn-floating').velocity(
+        { scaleY: ".4", scaleX: ".4", translateY: offsetY + 'px', translateX: offsetX + 'px'},
+        { duration: 0 });
+
+      var time = 0;
+      $this.find('ul .btn-floating').reverse().each( function () {
+        $(this).velocity(
+          { opacity: "1", scaleX: "1", scaleY: "1", translateY: "0", translateX: '0'},
+          { duration: 80, delay: time });
+        time += 40;
+      });
+    }
+  };
+
+  var closeFABMenu = function (btn) {
+    var $this = btn;
+    // Get direction option
+    var horizontal = $this.hasClass('horizontal');
+    var offsetY, offsetX;
+
+    if (horizontal === true) {
+      offsetX = 40;
+    } else {
+      offsetY = 40;
+    }
+
+    $this.removeClass('active');
+    var time = 0;
+    $this.find('ul .btn-floating').velocity("stop", true);
+    $this.find('ul .btn-floating').velocity(
+      { opacity: "0", scaleX: ".4", scaleY: ".4", translateY: offsetY + 'px', translateX: offsetX + 'px'},
+      { duration: 80 }
+    );
+  };
+
+
+  /**
+   * Transform FAB into toolbar
+   * @param  {Object}  object jQuery object
+   */
+  var FABtoToolbar = function(btn) {
+    if (btn.attr('data-open') === "true") {
+      return;
+    }
+
+    var offsetX, offsetY, scaleFactor;
+    var windowWidth = window.innerWidth;
+    var windowHeight = window.innerHeight;
+    var btnRect = btn[0].getBoundingClientRect();
+    var anchor = btn.find('> a').first();
+    var menu = btn.find('> ul').first();
+    var backdrop = $('<div class="fab-backdrop"></div>');
+    var fabColor = anchor.css('background-color');
+    anchor.append(backdrop);
+
+    offsetX = btnRect.left - (windowWidth / 2) + (btnRect.width / 2);
+    offsetY = windowHeight - btnRect.bottom;
+    scaleFactor = windowWidth / backdrop.width();
+    btn.attr('data-origin-bottom', btnRect.bottom);
+    btn.attr('data-origin-left', btnRect.left);
+    btn.attr('data-origin-width', btnRect.width);
+
+    // Set initial state
+    btn.addClass('active');
+    btn.attr('data-open', true);
+    btn.css({
+      'text-align': 'center',
+      width: '100%',
+      bottom: 0,
+      left: 0,
+      transform: 'translateX(' + offsetX + 'px)',
+      transition: 'none'
+    });
+    anchor.css({
+      transform: 'translateY(' + -offsetY + 'px)',
+      transition: 'none'
+    });
+    backdrop.css({
+      'background-color': fabColor
+    });
+
+
+    setTimeout(function() {
+      btn.css({
+        transform: '',
+        transition: 'transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s'
+      });
+      anchor.css({
+        overflow: 'visible',
+        transform: '',
+        transition: 'transform .2s'
+      });
+
+      setTimeout(function() {
+        btn.css({
+          overflow: 'hidden',
+          'background-color': fabColor
+        });
+        backdrop.css({
+          transform: 'scale(' + scaleFactor + ')',
+          transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)'
+        });
+        menu.find('> li > a').css({
+          opacity: 1
+        });
+
+        // Scroll to close.
+        $(window).on('scroll.fabToolbarClose', function() {
+          toolbarToFAB(btn);
+          $(window).off('scroll.fabToolbarClose');
+          $(document).off('click.fabToolbarClose');
+        });
+
+        $(document).on('click.fabToolbarClose', function(e) {
+          if (!$(e.target).closest(menu).length) {
+            toolbarToFAB(btn);
+            $(window).off('scroll.fabToolbarClose');
+            $(document).off('click.fabToolbarClose');
+          }
+        });
+      }, 100);
+    }, 0);
+  };
+
+  /**
+   * Transform toolbar back into FAB
+   * @param  {Object}  object jQuery object
+   */
+  var toolbarToFAB = function(btn) {
+    if (btn.attr('data-open') !== "true") {
+      return;
+    }
+
+    var offsetX, offsetY, scaleFactor;
+    var windowWidth = window.innerWidth;
+    var windowHeight = window.innerHeight;
+    var btnWidth = btn.attr('data-origin-width');
+    var btnBottom = btn.attr('data-origin-bottom');
+    var btnLeft = btn.attr('data-origin-left');
+    var anchor = btn.find('> .btn-floating').first();
+    var menu = btn.find('> ul').first();
+    var backdrop = btn.find('.fab-backdrop');
+    var fabColor = anchor.css('background-color');
+
+    offsetX = btnLeft - (windowWidth / 2) + (btnWidth / 2);
+    offsetY = windowHeight - btnBottom;
+    scaleFactor = windowWidth / backdrop.width();
+
+
+    // Hide backdrop
+    btn.removeClass('active');
+    btn.attr('data-open', false);
+    btn.css({
+      'background-color': 'transparent',
+      transition: 'none'
+    });
+    anchor.css({
+      transition: 'none'
+    });
+    backdrop.css({
+      transform: 'scale(0)',
+      'background-color': fabColor
+    });
+    menu.find('> li > a').css({
+      opacity: ''
+    });
+
+    setTimeout(function() {
+      backdrop.remove();
+
+      // Set initial state.
+      btn.css({
+        'text-align': '',
+        width: '',
+        bottom: '',
+        left: '',
+        overflow: '',
+        'background-color': '',
+        transform: 'translate3d(' + -offsetX + 'px,0,0)'
+      });
+      anchor.css({
+        overflow: '',
+        transform: 'translate3d(0,' + offsetY + 'px,0)'
+      });
+
+      setTimeout(function() {
+        btn.css({
+          transform: 'translate3d(0,0,0)',
+          transition: 'transform .2s'
+        });
+        anchor.css({
+          transform: 'translate3d(0,0,0)',
+          transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)'
+        });
+      }, 20);
+    }, 200);
+  };
+
+
+}( jQuery ));
+;(function ($) {
+  // Image transition function
+  Materialize.fadeInImage = function(selectorOrEl) {
+    var element;
+    if (typeof(selectorOrEl) === 'string') {
+      element = $(selectorOrEl);
+    } else if (typeof(selectorOrEl) === 'object') {
+      element = selectorOrEl;
+    } else {
+      return;
+    }
+    element.css({opacity: 0});
+    $(element).velocity({opacity: 1}, {
+      duration: 650,
+      queue: false,
+      easing: 'easeOutSine'
+    });
+    $(element).velocity({opacity: 1}, {
+      duration: 1300,
+      queue: false,
+      easing: 'swing',
+      step: function(now, fx) {
+        fx.start = 100;
+        var grayscale_setting = now/100;
+        var brightness_setting = 150 - (100 - now)/1.75;
+
+        if (brightness_setting < 100) {
+          brightness_setting = 100;
+        }
+        if (now >= 0) {
+          $(this).css({
+              "-webkit-filter": "grayscale("+grayscale_setting+")" + "brightness("+brightness_setting+"%)",
+              "filter": "grayscale("+grayscale_setting+")" + "brightness("+brightness_setting+"%)"
+          });
+        }
+      }
+    });
+  };
+
+  // Horizontal staggered list
+  Materialize.showStaggeredList = function(selectorOrEl) {
+    var element;
+    if (typeof(selectorOrEl) === 'string') {
+      element = $(selectorOrEl);
+    } else if (typeof(selectorOrEl) === 'object') {
+      element = selectorOrEl;
+    } else {
+      return;
+    }
+    var time = 0;
+    element.find('li').velocity(
+        { translateX: "-100px"},
+        { duration: 0 });
+
+    element.find('li').each(function() {
+      $(this).velocity(
+        { opacity: "1", translateX: "0"},
+        { duration: 800, delay: time, easing: [60, 10] });
+      time += 120;
+    });
+  };
+
+
+  $(document).ready(function() {
+    // Hardcoded .staggered-list scrollFire
+    // var staggeredListOptions = [];
+    // $('ul.staggered-list').each(function (i) {
+
+    //   var label = 'scrollFire-' + i;
+    //   $(this).addClass(label);
+    //   staggeredListOptions.push(
+    //     {selector: 'ul.staggered-list.' + label,
+    //      offset: 200,
+    //      callback: 'showStaggeredList("ul.staggered-list.' + label + '")'});
+    // });
+    // scrollFire(staggeredListOptions);
+
+    // HammerJS, Swipe navigation
+
+    // Touch Event
+    var swipeLeft = false;
+    var swipeRight = false;
+
+
+    // Dismissible Collections
+    $('.dismissable').each(function() {
+      $(this).hammer({
+        prevent_default: false
+      }).bind('pan', function(e) {
+        if (e.gesture.pointerType === "touch") {
+          var $this = $(this);
+          var direction = e.gesture.direction;
+          var x = e.gesture.deltaX;
+          var velocityX = e.gesture.velocityX;
+
+          $this.velocity({ translateX: x
+              }, {duration: 50, queue: false, easing: 'easeOutQuad'});
+
+          // Swipe Left
+          if (direction === 4 && (x > ($this.innerWidth() / 2) || velocityX < -0.75)) {
+            swipeLeft = true;
+          }
+
+          // Swipe Right
+          if (direction === 2 && (x < (-1 * $this.innerWidth() / 2) || velocityX > 0.75)) {
+            swipeRight = true;
+          }
+        }
+      }).bind('panend', function(e) {
+        // Reset if collection is moved back into original position
+        if (Math.abs(e.gesture.deltaX) < ($(this).innerWidth() / 2)) {
+          swipeRight = false;
+          swipeLeft = false;
+        }
+
+        if (e.gesture.pointerType === "touch") {
+          var $this = $(this);
+          if (swipeLeft || swipeRight) {
+            var fullWidth;
+            if (swipeLeft) { fullWidth = $this.innerWidth(); }
+            else { fullWidth = -1 * $this.innerWidth(); }
+
+            $this.velocity({ translateX: fullWidth,
+              }, {duration: 100, queue: false, easing: 'easeOutQuad', complete:
+              function() {
+                $this.css('border', 'none');
+                $this.velocity({ height: 0, padding: 0,
+                  }, {duration: 200, queue: false, easing: 'easeOutQuad', complete:
+                    function() { $this.remove(); }
+                  });
+              }
+            });
+          }
+          else {
+            $this.velocity({ translateX: 0,
+              }, {duration: 100, queue: false, easing: 'easeOutQuad'});
+          }
+          swipeLeft = false;
+          swipeRight = false;
+        }
+      });
+
+    });
+
+
+    // time = 0
+    // // Vertical Staggered list
+    // $('ul.staggered-list.vertical li').velocity(
+    //     { translateY: "100px"},
+    //     { duration: 0 });
+
+    // $('ul.staggered-list.vertical li').each(function() {
+    //   $(this).velocity(
+    //     { opacity: "1", translateY: "0"},
+    //     { duration: 800, delay: time, easing: [60, 25] });
+    //   time += 120;
+    // });
+
+    // // Fade in and Scale
+    // $('.fade-in.scale').velocity(
+    //     { scaleX: .4, scaleY: .4, translateX: -600},
+    //     { duration: 0});
+    // $('.fade-in').each(function() {
+    //   $(this).velocity(
+    //     { opacity: "1", scaleX: 1, scaleY: 1, translateX: 0},
+    //     { duration: 800, easing: [60, 10] });
+    // });
+  });
+}( jQuery ));
+;(function($) {
+
+  var scrollFireEventsHandled = false;
+
+  // Input: Array of JSON objects {selector, offset, callback}
+  Materialize.scrollFire = function(options) {
+    var onScroll = function() {
+      var windowScroll = window.pageYOffset + window.innerHeight;
+
+      for (var i = 0 ; i < options.length; i++) {
+        // Get options from each line
+        var value = options[i];
+        var selector = value.selector,
+            offset = value.offset,
+            callback = value.callback;
+
+        var currentElement = document.querySelector(selector);
+        if ( currentElement !== null) {
+          var elementOffset = currentElement.getBoundingClientRect().top + window.pageYOffset;
+
+          if (windowScroll > (elementOffset + offset)) {
+            if (value.done !== true) {
+              if (typeof(callback) === 'function') {
+                callback.call(this, currentElement);
+              } else if (typeof(callback) === 'string') {
+                var callbackFunc = new Function(callback);
+                callbackFunc(currentElement);
+              }
+              value.done = true;
+            }
+          }
+        }
+      }
+    };
+
+
+    var throttledScroll = Materialize.throttle(function() {
+      onScroll();
+    }, options.throttle || 100);
+
+    if (!scrollFireEventsHandled) {
+      window.addEventListener("scroll", throttledScroll);
+      window.addEventListener("resize", throttledScroll);
+      scrollFireEventsHandled = true;
+    }
+
+    // perform a scan once, after current execution context, and after dom is ready
+    setTimeout(throttledScroll, 0);
+  };
+
+})(jQuery);
+;/*!
+ * pickadate.js v3.5.0, 2014/04/13
+ * By Amsul, http://amsul.ca
+ * Hosted on http://amsul.github.io/pickadate.js
+ * Licensed under MIT
+ */
+
+(function ( factory ) {
+
+    // AMD.
+    if ( typeof define == 'function' && define.amd )
+        define( 'picker', ['jquery'], factory )
+
+    // Node.js/browserify.
+    else if ( typeof exports == 'object' )
+        module.exports = factory( require('jquery') )
+
+    // Browser globals.
+    else this.Picker = factory( jQuery )
+
+}(function( $ ) {
+
+var $window = $( window )
+var $document = $( document )
+var $html = $( document.documentElement )
+
+
+/**
+ * The picker constructor that creates a blank picker.
+ */
+function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
+
+    // If there’s no element, return the picker constructor.
+    if ( !ELEMENT ) return PickerConstructor
+
+
+    var
+        IS_DEFAULT_THEME = false,
+
+
+        // The state of the picker.
+        STATE = {
+            id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) )
+        },
+
+
+        // Merge the defaults and options passed.
+        SETTINGS = COMPONENT ? $.extend( true, {}, COMPONENT.defaults, OPTIONS ) : OPTIONS || {},
+
+
+        // Merge the default classes with the settings classes.
+        CLASSES = $.extend( {}, PickerConstructor.klasses(), SETTINGS.klass ),
+
+
+        // The element node wrapper into a jQuery object.
+        $ELEMENT = $( ELEMENT ),
+
+
+        // Pseudo picker constructor.
+        PickerInstance = function() {
+            return this.start()
+        },
+
+
+        // The picker prototype.
+        P = PickerInstance.prototype = {
+
+            constructor: PickerInstance,
+
+            $node: $ELEMENT,
+
+
+            /**
+             * Initialize everything
+             */
+            start: function() {
+
+                // If it’s already started, do nothing.
+                if ( STATE && STATE.start ) return P
+
+
+                // Update the picker states.
+                STATE.methods = {}
+                STATE.start = true
+                STATE.open = false
+                STATE.type = ELEMENT.type
+
+
+                // Confirm focus state, convert into text input to remove UA stylings,
+                // and set as readonly to prevent keyboard popup.
+                ELEMENT.autofocus = ELEMENT == getActiveElement()
+                ELEMENT.readOnly = !SETTINGS.editable
+                ELEMENT.id = ELEMENT.id || STATE.id
+                if ( ELEMENT.type != 'text' ) {
+                    ELEMENT.type = 'text'
+                }
+
+
+                // Create a new picker component with the settings.
+                P.component = new COMPONENT(P, SETTINGS)
+
+
+                // Create the picker root with a holder and then prepare it.
+                P.$root = $( PickerConstructor._.node('div', createWrappedComponent(), CLASSES.picker, 'id="' + ELEMENT.id + '_root" tabindex="0"') )
+                prepareElementRoot()
+
+
+                // If there’s a format for the hidden input element, create the element.
+                if ( SETTINGS.formatSubmit ) {
+                    prepareElementHidden()
+                }
+
+
+                // Prepare the input element.
+                prepareElement()
+
+
+                // Insert the root as specified in the settings.
+                if ( SETTINGS.container ) $( SETTINGS.container ).append( P.$root )
+                else $ELEMENT.after( P.$root )
+
+
+                // Bind the default component and settings events.
+                P.on({
+                    start: P.component.onStart,
+                    render: P.component.onRender,
+                    stop: P.component.onStop,
+                    open: P.component.onOpen,
+                    close: P.component.onClose,
+                    set: P.component.onSet
+                }).on({
+                    start: SETTINGS.onStart,
+                    render: SETTINGS.onRender,
+                    stop: SETTINGS.onStop,
+                    open: SETTINGS.onOpen,
+                    close: SETTINGS.onClose,
+                    set: SETTINGS.onSet
+                })
+
+
+                // Once we’re all set, check the theme in use.
+                IS_DEFAULT_THEME = isUsingDefaultTheme( P.$root.children()[ 0 ] )
+
+
+                // If the element has autofocus, open the picker.
+                if ( ELEMENT.autofocus ) {
+                    P.open()
+                }
+
+
+                // Trigger queued the “start” and “render” events.
+                return P.trigger( 'start' ).trigger( 'render' )
+            }, //start
+
+
+            /**
+             * Render a new picker
+             */
+            render: function( entireComponent ) {
+
+                // Insert a new component holder in the root or box.
+                if ( entireComponent ) P.$root.html( createWrappedComponent() )
+                else P.$root.find( '.' + CLASSES.box ).html( P.component.nodes( STATE.open ) )
+
+                // Trigger the queued “render” events.
+                return P.trigger( 'render' )
+            }, //render
+
+
+            /**
+             * Destroy everything
+             */
+            stop: function() {
+
+                // If it’s already stopped, do nothing.
+                if ( !STATE.start ) return P
+
+                // Then close the picker.
+                P.close()
+
+                // Remove the hidden field.
+                if ( P._hidden ) {
+                    P._hidden.parentNode.removeChild( P._hidden )
+                }
+
+                // Remove the root.
+                P.$root.remove()
+
+                // Remove the input class, remove the stored data, and unbind
+                // the events (after a tick for IE - see `P.close`).
+                $ELEMENT.removeClass( CLASSES.input ).removeData( NAME )
+                setTimeout( function() {
+                    $ELEMENT.off( '.' + STATE.id )
+                }, 0)
+
+                // Restore the element state
+                ELEMENT.type = STATE.type
+                ELEMENT.readOnly = false
+
+                // Trigger the queued “stop” events.
+                P.trigger( 'stop' )
+
+                // Reset the picker states.
+                STATE.methods = {}
+                STATE.start = false
+
+                return P
+            }, //stop
+
+
+            /**
+             * Open up the picker
+             */
+            open: function( dontGiveFocus ) {
+
+                // If it’s already open, do nothing.
+                if ( STATE.open ) return P
+
+                // Add the “active” class.
+                $ELEMENT.addClass( CLASSES.active )
+                aria( ELEMENT, 'expanded', true )
+
+                // * A Firefox bug, when `html` has `overflow:hidden`, results in
+                //   killing transitions :(. So add the “opened” state on the next tick.
+                //   Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289
+                setTimeout( function() {
+
+                    // Add the “opened” class to the picker root.
+                    P.$root.addClass( CLASSES.opened )
+                    aria( P.$root[0], 'hidden', false )
+
+                }, 0 )
+
+                // If we have to give focus, bind the element and doc events.
+                if ( dontGiveFocus !== false ) {
+
+                    // Set it as open.
+                    STATE.open = true
+
+                    // Prevent the page from scrolling.
+                    if ( IS_DEFAULT_THEME ) {
+                        $html.
+                            css( 'overflow', 'hidden' ).
+                            css( 'padding-right', '+=' + getScrollbarWidth() )
+                    }
+
+                    // Pass focus to the root element’s jQuery object.
+                    // * Workaround for iOS8 to bring the picker’s root into view.
+                    P.$root.eq(0).focus()
+
+                    // Bind the document events.
+                    $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) {
+
+                        var target = event.target
+
+                        // If the target of the event is not the element, close the picker picker.
+                        // * Don’t worry about clicks or focusins on the root because those don’t bubble up.
+                        //   Also, for Firefox, a click on an `option` element bubbles up directly
+                        //   to the doc. So make sure the target wasn't the doc.
+                        // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling,
+                        //   which causes the picker to unexpectedly close when right-clicking it. So make
+                        //   sure the event wasn’t a right-click.
+                        if ( target != ELEMENT && target != document && event.which != 3 ) {
+
+                            // If the target was the holder that covers the screen,
+                            // keep the element focused to maintain tabindex.
+                            P.close( target === P.$root.children()[0] )
+                        }
+
+                    }).on( 'keydown.' + STATE.id, function( event ) {
+
+                        var
+                            // Get the keycode.
+                            keycode = event.keyCode,
+
+                            // Translate that to a selection change.
+                            keycodeToMove = P.component.key[ keycode ],
+
+                            // Grab the target.
+                            target = event.target
+
+
+                        // On escape, close the picker and give focus.
+                        if ( keycode == 27 ) {
+                            P.close( true )
+                        }
+
+
+                        // Check if there is a key movement or “enter” keypress on the element.
+                        else if ( target == P.$root[0] && ( keycodeToMove || keycode == 13 ) ) {
+
+                            // Prevent the default action to stop page movement.
+                            event.preventDefault()
+
+                            // Trigger the key movement action.
+                            if ( keycodeToMove ) {
+                                PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] )
+                            }
+
+                            // On “enter”, if the highlighted item isn’t disabled, set the value and close.
+                            else if ( !P.$root.find( '.' + CLASSES.highlighted ).hasClass( CLASSES.disabled ) ) {
+                                P.set( 'select', P.component.item.highlight ).close()
+                            }
+                        }
+
+
+                        // If the target is within the root and “enter” is pressed,
+                        // prevent the default action and trigger a click on the target instead.
+                        else if ( $.contains( P.$root[0], target ) && keycode == 13 ) {
+                            event.preventDefault()
+                            target.click()
+                        }
+                    })
+                }
+
+                // Trigger the queued “open” events.
+                return P.trigger( 'open' )
+            }, //open
+
+
+            /**
+             * Close the picker
+             */
+            close: function( giveFocus ) {
+
+                // If we need to give focus, do it before changing states.
+                if ( giveFocus ) {
+                    // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :|
+                    // The focus is triggered *after* the close has completed - causing it
+                    // to open again. So unbind and rebind the event at the next tick.
+                    P.$root.off( 'focus.toOpen' ).eq(0).focus()
+                    setTimeout( function() {
+                        P.$root.on( 'focus.toOpen', handleFocusToOpenEvent )
+                    }, 0 )
+                }
+
+                // Remove the “active” class.
+                $ELEMENT.removeClass( CLASSES.active )
+                aria( ELEMENT, 'expanded', false )
+
+                // * A Firefox bug, when `html` has `overflow:hidden`, results in
+                //   killing transitions :(. So remove the “opened” state on the next tick.
+                //   Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289
+                setTimeout( function() {
+
+                    // Remove the “opened” and “focused” class from the picker root.
+                    P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused )
+                    aria( P.$root[0], 'hidden', true )
+
+                }, 0 )
+
+                // If it’s already closed, do nothing more.
+                if ( !STATE.open ) return P
+
+                // Set it as closed.
+                STATE.open = false
+
+                // Allow the page to scroll.
+                if ( IS_DEFAULT_THEME ) {
+                    $html.
+                        css( 'overflow', '' ).
+                        css( 'padding-right', '-=' + getScrollbarWidth() )
+                }
+
+                // Unbind the document events.
+                $document.off( '.' + STATE.id )
+
+                // Trigger the queued “close” events.
+                return P.trigger( 'close' )
+            }, //close
+
+
+            /**
+             * Clear the values
+             */
+            clear: function( options ) {
+                return P.set( 'clear', null, options )
+            }, //clear
+
+
+            /**
+             * Set something
+             */
+            set: function( thing, value, options ) {
+
+                var thingItem, thingValue,
+                    thingIsObject = $.isPlainObject( thing ),
+                    thingObject = thingIsObject ? thing : {}
+
+                // Make sure we have usable options.
+                options = thingIsObject && $.isPlainObject( value ) ? value : options || {}
+
+                if ( thing ) {
+
+                    // If the thing isn’t an object, make it one.
+                    if ( !thingIsObject ) {
+                        thingObject[ thing ] = value
+                    }
+
+                    // Go through the things of items to set.
+                    for ( thingItem in thingObject ) {
+
+                        // Grab the value of the thing.
+                        thingValue = thingObject[ thingItem ]
+
+                        // First, if the item exists and there’s a value, set it.
+                        if ( thingItem in P.component.item ) {
+                            if ( thingValue === undefined ) thingValue = null
+                            P.component.set( thingItem, thingValue, options )
+                        }
+
+                        // Then, check to update the element value and broadcast a change.
+                        if ( thingItem == 'select' || thingItem == 'clear' ) {
+                            $ELEMENT.
+                                val( thingItem == 'clear' ? '' : P.get( thingItem, SETTINGS.format ) ).
+                                trigger( 'change' )
+                        }
+                    }
+
+                    // Render a new picker.
+                    P.render()
+                }
+
+                // When the method isn’t muted, trigger queued “set” events and pass the `thingObject`.
+                return options.muted ? P : P.trigger( 'set', thingObject )
+            }, //set
+
+
+            /**
+             * Get something
+             */
+            get: function( thing, format ) {
+
+                // Make sure there’s something to get.
+                thing = thing || 'value'
+
+                // If a picker state exists, return that.
+                if ( STATE[ thing ] != null ) {
+                    return STATE[ thing ]
+                }
+
+                // Return the submission value, if that.
+                if ( thing == 'valueSubmit' ) {
+                    if ( P._hidden ) {
+                        return P._hidden.value
+                    }
+                    thing = 'value'
+                }
+
+                // Return the value, if that.
+                if ( thing == 'value' ) {
+                    return ELEMENT.value
+                }
+
+                // Check if a component item exists, return that.
+                if ( thing in P.component.item ) {
+                    if ( typeof format == 'string' ) {
+                        var thingValue = P.component.get( thing )
+                        return thingValue ?
+                            PickerConstructor._.trigger(
+                                P.component.formats.toString,
+                                P.component,
+                                [ format, thingValue ]
+                            ) : ''
+                    }
+                    return P.component.get( thing )
+                }
+            }, //get
+
+
+
+            /**
+             * Bind events on the things.
+             */
+            on: function( thing, method, internal ) {
+
+                var thingName, thingMethod,
+                    thingIsObject = $.isPlainObject( thing ),
+                    thingObject = thingIsObject ? thing : {}
+
+                if ( thing ) {
+
+                    // If the thing isn’t an object, make it one.
+                    if ( !thingIsObject ) {
+                        thingObject[ thing ] = method
+                    }
+
+                    // Go through the things to bind to.
+                    for ( thingName in thingObject ) {
+
+                        // Grab the method of the thing.
+                        thingMethod = thingObject[ thingName ]
+
+                        // If it was an internal binding, prefix it.
+                        if ( internal ) {
+                            thingName = '_' + thingName
+                        }
+
+                        // Make sure the thing methods collection exists.
+                        STATE.methods[ thingName ] = STATE.methods[ thingName ] || []
+
+                        // Add the method to the relative method collection.
+                        STATE.methods[ thingName ].push( thingMethod )
+                    }
+                }
+
+                return P
+            }, //on
+
+
+
+            /**
+             * Unbind events on the things.
+             */
+            off: function() {
+                var i, thingName,
+                    names = arguments;
+                for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) {
+                    thingName = names[i]
+                    if ( thingName in STATE.methods ) {
+                        delete STATE.methods[thingName]
+                    }
+                }
+                return P
+            },
+
+
+            /**
+             * Fire off method events.
+             */
+            trigger: function( name, data ) {
+                var _trigger = function( name ) {
+                    var methodList = STATE.methods[ name ]
+                    if ( methodList ) {
+                        methodList.map( function( method ) {
+                            PickerConstructor._.trigger( method, P, [ data ] )
+                        })
+                    }
+                }
+                _trigger( '_' + name )
+                _trigger( name )
+                return P
+            } //trigger
+        } //PickerInstance.prototype
+
+
+    /**
+     * Wrap the picker holder components together.
+     */
+    function createWrappedComponent() {
+
+        // Create a picker wrapper holder
+        return PickerConstructor._.node( 'div',
+
+            // Create a picker wrapper node
+            PickerConstructor._.node( 'div',
+
+                // Create a picker frame
+                PickerConstructor._.node( 'div',
+
+                    // Create a picker box node
+                    PickerConstructor._.node( 'div',
+
+                        // Create the components nodes.
+                        P.component.nodes( STATE.open ),
+
+                        // The picker box class
+                        CLASSES.box
+                    ),
+
+                    // Picker wrap class
+                    CLASSES.wrap
+                ),
+
+                // Picker frame class
+                CLASSES.frame
+            ),
+
+            // Picker holder class
+            CLASSES.holder
+        ) //endreturn
+    } //createWrappedComponent
+
+
+
+    /**
+     * Prepare the input element with all bindings.
+     */
+    function prepareElement() {
+
+        $ELEMENT.
+
+            // Store the picker data by component name.
+            data(NAME, P).
+
+            // Add the “input” class name.
+            addClass(CLASSES.input).
+
+            // Remove the tabindex.
+            attr('tabindex', -1).
+
+            // If there’s a `data-value`, update the value of the element.
+            val( $ELEMENT.data('value') ?
+                P.get('select', SETTINGS.format) :
+                ELEMENT.value
+            )
+
+
+        // Only bind keydown events if the element isn’t editable.
+        if ( !SETTINGS.editable ) {
+
+            $ELEMENT.
+
+                // On focus/click, focus onto the root to open it up.
+                on( 'focus.' + STATE.id + ' click.' + STATE.id, function( event ) {
+                    event.preventDefault()
+                    P.$root.eq(0).focus()
+                }).
+
+                // Handle keyboard event based on the picker being opened or not.
+                on( 'keydown.' + STATE.id, handleKeydownEvent )
+        }
+
+
+        // Update the aria attributes.
+        aria(ELEMENT, {
+            haspopup: true,
+            expanded: false,
+            readonly: false,
+            owns: ELEMENT.id + '_root'
+        })
+    }
+
+
+    /**
+     * Prepare the root picker element with all bindings.
+     */
+    function prepareElementRoot() {
+
+        P.$root.
+
+            on({
+
+                // For iOS8.
+                keydown: handleKeydownEvent,
+
+                // When something within the root is focused, stop from bubbling
+                // to the doc and remove the “focused” state from the root.
+                focusin: function( event ) {
+                    P.$root.removeClass( CLASSES.focused )
+                    event.stopPropagation()
+                },
+
+                // When something within the root holder is clicked, stop it
+                // from bubbling to the doc.
+                'mousedown click': function( event ) {
+
+                    var target = event.target
+
+                    // Make sure the target isn’t the root holder so it can bubble up.
+                    if ( target != P.$root.children()[ 0 ] ) {
+
+                        event.stopPropagation()
+
+                        // * For mousedown events, cancel the default action in order to
+                        //   prevent cases where focus is shifted onto external elements
+                        //   when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
+                        //   Also, for Firefox, don’t prevent action on the `option` element.
+                        if ( event.type == 'mousedown' && !$( target ).is( 'input, select, textarea, button, option' )) {
+
+                            event.preventDefault()
+
+                            // Re-focus onto the root so that users can click away
+                            // from elements focused within the picker.
+                            P.$root.eq(0).focus()
+                        }
+                    }
+                }
+            }).
+
+            // Add/remove the “target” class on focus and blur.
+            on({
+                focus: function() {
+                    $ELEMENT.addClass( CLASSES.target )
+                },
+                blur: function() {
+                    $ELEMENT.removeClass( CLASSES.target )
+                }
+            }).
+
+            // Open the picker and adjust the root “focused” state
+            on( 'focus.toOpen', handleFocusToOpenEvent ).
+
+            // If there’s a click on an actionable element, carry out the actions.
+            on( 'click', '[data-pick], [data-nav], [data-clear], [data-close]', function() {
+
+                var $target = $( this ),
+                    targetData = $target.data(),
+                    targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),
+
+                    // * For IE, non-focusable elements can be active elements as well
+                    //   (http://stackoverflow.com/a/2684561).
+                    activeElement = getActiveElement()
+                    activeElement = activeElement && ( activeElement.type || activeElement.href )
+
+                // If it’s disabled or nothing inside is actively focused, re-focus the element.
+                if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
+                    P.$root.eq(0).focus()
+                }
+
+                // If something is superficially changed, update the `highlight` based on the `nav`.
+                if ( !targetDisabled && targetData.nav ) {
+                    P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
+                }
+
+                // If something is picked, set `select` then close with focus.
+                else if ( !targetDisabled && 'pick' in targetData ) {
+                    P.set( 'select', targetData.pick )
+                }
+
+                // If a “clear” button is pressed, empty the values and close with focus.
+                else if ( targetData.clear ) {
+                    P.clear().close( true )
+                }
+
+                else if ( targetData.close ) {
+                    P.close( true )
+                }
+
+            }) //P.$root
+
+        aria( P.$root[0], 'hidden', true )
+    }
+
+
+     /**
+      * Prepare the hidden input element along with all bindings.
+      */
+    function prepareElementHidden() {
+
+        var name
+
+        if ( SETTINGS.hiddenName === true ) {
+            name = ELEMENT.name
+            ELEMENT.name = ''
+        }
+        else {
+            name = [
+                typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '',
+                typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit'
+            ]
+            name = name[0] + ELEMENT.name + name[1]
+        }
+
+        P._hidden = $(
+            '<input ' +
+            'type=hidden ' +
+
+            // Create the name using the original input’s with a prefix and suffix.
+            'name="' + name + '"' +
+
+            // If the element has a value, set the hidden value as well.
+            (
+                $ELEMENT.data('value') || ELEMENT.value ?
+                    ' value="' + P.get('select', SETTINGS.formatSubmit) + '"' :
+                    ''
+            ) +
+            '>'
+        )[0]
+
+        $ELEMENT.
+
+            // If the value changes, update the hidden input with the correct format.
+            on('change.' + STATE.id, function() {
+                P._hidden.value = ELEMENT.value ?
+                    P.get('select', SETTINGS.formatSubmit) :
+                    ''
+            })
+
+
+        // Insert the hidden input as specified in the settings.
+        if ( SETTINGS.container ) $( SETTINGS.container ).append( P._hidden )
+        else $ELEMENT.after( P._hidden )
+    }
+
+
+    // For iOS8.
+    function handleKeydownEvent( event ) {
+
+        var keycode = event.keyCode,
+
+            // Check if one of the delete keys was pressed.
+            isKeycodeDelete = /^(8|46)$/.test(keycode)
+
+        // For some reason IE clears the input value on “escape”.
+        if ( keycode == 27 ) {
+            P.close()
+            return false
+        }
+
+        // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
+        if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) {
+
+            // Prevent it from moving the page and bubbling to doc.
+            event.preventDefault()
+            event.stopPropagation()
+
+            // If `delete` was pressed, clear the values and close the picker.
+            // Otherwise open the picker.
+            if ( isKeycodeDelete ) { P.clear().close() }
+            else { P.open() }
+        }
+    }
+
+
+    // Separated for IE
+    function handleFocusToOpenEvent( event ) {
+
+        // Stop the event from propagating to the doc.
+        event.stopPropagation()
+
+        // If it’s a focus event, add the “focused” class to the root.
+        if ( event.type == 'focus' ) {
+            P.$root.addClass( CLASSES.focused )
+        }
+
+        // And then finally open the picker.
+        P.open()
+    }
+
+
+    // Return a new picker instance.
+    return new PickerInstance()
+} //PickerConstructor
+
+
+
+/**
+ * The default classes and prefix to use for the HTML classes.
+ */
+PickerConstructor.klasses = function( prefix ) {
+    prefix = prefix || 'picker'
+    return {
+
+        picker: prefix,
+        opened: prefix + '--opened',
+        focused: prefix + '--focused',
+
+        input: prefix + '__input',
+        active: prefix + '__input--active',
+        target: prefix + '__input--target',
+
+        holder: prefix + '__holder',
+
+        frame: prefix + '__frame',
+        wrap: prefix + '__wrap',
+
+        box: prefix + '__box'
+    }
+} //PickerConstructor.klasses
+
+
+
+/**
+ * Check if the default theme is being used.
+ */
+function isUsingDefaultTheme( element ) {
+
+    var theme,
+        prop = 'position'
+
+    // For IE.
+    if ( element.currentStyle ) {
+        theme = element.currentStyle[prop]
+    }
+
+    // For normal browsers.
+    else if ( window.getComputedStyle ) {
+        theme = getComputedStyle( element )[prop]
+    }
+
+    return theme == 'fixed'
+}
+
+
+
+/**
+ * Get the width of the browser’s scrollbar.
+ * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js
+ */
+function getScrollbarWidth() {
+
+    if ( $html.height() <= $window.height() ) {
+        return 0
+    }
+
+    var $outer = $( '<div style="visibility:hidden;width:100px" />' ).
+        appendTo( 'body' )
+
+    // Get the width without scrollbars.
+    var widthWithoutScroll = $outer[0].offsetWidth
+
+    // Force adding scrollbars.
+    $outer.css( 'overflow', 'scroll' )
+
+    // Add the inner div.
+    var $inner = $( '<div style="width:100%" />' ).appendTo( $outer )
+
+    // Get the width with scrollbars.
+    var widthWithScroll = $inner[0].offsetWidth
+
+    // Remove the divs.
+    $outer.remove()
+
+    // Return the difference between the widths.
+    return widthWithoutScroll - widthWithScroll
+}
+
+
+
+/**
+ * PickerConstructor helper methods.
+ */
+PickerConstructor._ = {
+
+    /**
+     * Create a group of nodes. Expects:
+     * `
+        {
+            min:    {Integer},
+            max:    {Integer},
+            i:      {Integer},
+            node:   {String},
+            item:   {Function}
+        }
+     * `
+     */
+    group: function( groupObject ) {
+
+        var
+            // Scope for the looped object
+            loopObjectScope,
+
+            // Create the nodes list
+            nodesList = '',
+
+            // The counter starts from the `min`
+            counter = PickerConstructor._.trigger( groupObject.min, groupObject )
+
+
+        // Loop from the `min` to `max`, incrementing by `i`
+        for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) {
+
+            // Trigger the `item` function within scope of the object
+            loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] )
+
+            // Splice the subgroup and create nodes out of the sub nodes
+            nodesList += PickerConstructor._.node(
+                groupObject.node,
+                loopObjectScope[ 0 ],   // the node
+                loopObjectScope[ 1 ],   // the classes
+                loopObjectScope[ 2 ]    // the attributes
+            )
+        }
+
+        // Return the list of nodes
+        return nodesList
+    }, //group
+
+
+    /**
+     * Create a dom node string
+     */
+    node: function( wrapper, item, klass, attribute ) {
+
+        // If the item is false-y, just return an empty string
+        if ( !item ) return ''
+
+        // If the item is an array, do a join
+        item = $.isArray( item ) ? item.join( '' ) : item
+
+        // Check for the class
+        klass = klass ? ' class="' + klass + '"' : ''
+
+        // Check for any attributes
+        attribute = attribute ? ' ' + attribute : ''
+
+        // Return the wrapped item
+        return '<' + wrapper + klass + attribute + '>' + item + '</' + wrapper + '>'
+    }, //node
+
+
+    /**
+     * Lead numbers below 10 with a zero.
+     */
+    lead: function( number ) {
+        return ( number < 10 ? '0': '' ) + number
+    },
+
+
+    /**
+     * Trigger a function otherwise return the value.
+     */
+    trigger: function( callback, scope, args ) {
+        return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback
+    },
+
+
+    /**
+     * If the second character is a digit, length is 2 otherwise 1.
+     */
+    digits: function( string ) {
+        return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1
+    },
+
+
+    /**
+     * Tell if something is a date object.
+     */
+    isDate: function( value ) {
+        return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() )
+    },
+
+
+    /**
+     * Tell if something is an integer.
+     */
+    isInteger: function( value ) {
+        return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0
+    },
+
+
+    /**
+     * Create ARIA attribute strings.
+     */
+    ariaAttr: ariaAttr
+} //PickerConstructor._
+
+
+
+/**
+ * Extend the picker with a component and defaults.
+ */
+PickerConstructor.extend = function( name, Component ) {
+
+    // Extend jQuery.
+    $.fn[ name ] = function( options, action ) {
+
+        // Grab the component data.
+        var componentData = this.data( name )
+
+        // If the picker is requested, return the data object.
+        if ( options == 'picker' ) {
+            return componentData
+        }
+
+        // If the component data exists and `options` is a string, carry out the action.
+        if ( componentData && typeof options == 'string' ) {
+            return PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] )
+        }
+
+        // Otherwise go through each matched element and if the component
+        // doesn’t exist, create a new picker using `this` element
+        // and merging the defaults and options with a deep copy.
+        return this.each( function() {
+            var $this = $( this )
+            if ( !$this.data( name ) ) {
+                new PickerConstructor( this, name, Component, options )
+            }
+        })
+    }
+
+    // Set the defaults.
+    $.fn[ name ].defaults = Component.defaults
+} //PickerConstructor.extend
+
+
+
+function aria(element, attribute, value) {
+    if ( $.isPlainObject(attribute) ) {
+        for ( var key in attribute ) {
+            ariaSet(element, key, attribute[key])
+        }
+    }
+    else {
+        ariaSet(element, attribute, value)
+    }
+}
+function ariaSet(element, attribute, value) {
+    element.setAttribute(
+        (attribute == 'role' ? '' : 'aria-') + attribute,
+        value
+    )
+}
+function ariaAttr(attribute, data) {
+    if ( !$.isPlainObject(attribute) ) {
+        attribute = { attribute: data }
+    }
+    data = ''
+    for ( var key in attribute ) {
+        var attr = (key == 'role' ? '' : 'aria-') + key,
+            attrVal = attribute[key]
+        data += attrVal == null ? '' : attr + '="' + attribute[key] + '"'
+    }
+    return data
+}
+
+// IE8 bug throws an error for activeElements within iframes.
+function getActiveElement() {
+    try {
+        return document.activeElement
+    } catch ( err ) { }
+}
+
+
+
+// Expose the picker constructor.
+return PickerConstructor
+
+
+}));
+
+
+;/*!
+ * Date picker for pickadate.js v3.5.0
+ * http://amsul.github.io/pickadate.js/date.htm
+ */
+
+(function ( factory ) {
+
+    // AMD.
+    if ( typeof define == 'function' && define.amd )
+        define( ['picker', 'jquery'], factory )
+
+    // Node.js/browserify.
+    else if ( typeof exports == 'object' )
+        module.exports = factory( require('./picker.js'), require('jquery') )
+
+    // Browser globals.
+    else factory( Picker, jQuery )
+
+}(function( Picker, $ ) {
+
+
+/**
+ * Globals and constants
+ */
+var DAYS_IN_WEEK = 7,
+    WEEKS_IN_CALENDAR = 6,
+    _ = Picker._
+
+
+
+/**
+ * The date picker constructor
+ */
+function DatePicker( picker, settings ) {
+
+    var calendar = this,
+        element = picker.$node[ 0 ],
+        elementValue = element.value,
+        elementDataValue = picker.$node.data( 'value' ),
+        valueString = elementDataValue || elementValue,
+        formatString = elementDataValue ? settings.formatSubmit : settings.format,
+        isRTL = function() {
+
+            return element.currentStyle ?
+
+                // For IE.
+                element.currentStyle.direction == 'rtl' :
+
+                // For normal browsers.
+                getComputedStyle( picker.$root[0] ).direction == 'rtl'
+        }
+
+    calendar.settings = settings
+    calendar.$node = picker.$node
+
+    // The queue of methods that will be used to build item objects.
+    calendar.queue = {
+        min: 'measure create',
+        max: 'measure create',
+        now: 'now create',
+        select: 'parse create validate',
+        highlight: 'parse navigate create validate',
+        view: 'parse create validate viewset',
+        disable: 'deactivate',
+        enable: 'activate'
+    }
+
+    // The component's item object.
+    calendar.item = {}
+
+    calendar.item.clear = null
+    calendar.item.disable = ( settings.disable || [] ).slice( 0 )
+    calendar.item.enable = -(function( collectionDisabled ) {
+        return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1
+    })( calendar.item.disable )
+
+    calendar.
+        set( 'min', settings.min ).
+        set( 'max', settings.max ).
+        set( 'now' )
+
+    // When there’s a value, set the `select`, which in turn
+    // also sets the `highlight` and `view`.
+    if ( valueString ) {
+        calendar.set( 'select', valueString, { format: formatString })
+    }
+
+    // If there’s no value, default to highlighting “today”.
+    else {
+        calendar.
+            set( 'select', null ).
+            set( 'highlight', calendar.item.now )
+    }
+
+
+    // The keycode to movement mapping.
+    calendar.key = {
+        40: 7, // Down
+        38: -7, // Up
+        39: function() { return isRTL() ? -1 : 1 }, // Right
+        37: function() { return isRTL() ? 1 : -1 }, // Left
+        go: function( timeChange ) {
+            var highlightedObject = calendar.item.highlight,
+                targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange )
+            calendar.set(
+                'highlight',
+                targetDate,
+                { interval: timeChange }
+            )
+            this.render()
+        }
+    }
+
+
+    // Bind some picker events.
+    picker.
+        on( 'render', function() {
+            picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() {
+                var value = this.value
+                if ( value ) {
+                    picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] )
+                    picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' )
+                }
+            })
+            picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() {
+                var value = this.value
+                if ( value ) {
+                    picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] )
+                    picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' )
+                }
+            })
+        }, 1 ).
+        on( 'open', function() {
+            var includeToday = ''
+            if ( calendar.disabled( calendar.get('now') ) ) {
+                includeToday = ':not(.' + settings.klass.buttonToday + ')'
+            }
+            picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false )
+        }, 1 ).
+        on( 'close', function() {
+            picker.$root.find( 'button, select' ).attr( 'disabled', true )
+        }, 1 )
+
+} //DatePicker
+
+
+/**
+ * Set a datepicker item object.
+ */
+DatePicker.prototype.set = function( type, value, options ) {
+
+    var calendar = this,
+        calendarItem = calendar.item
+
+    // If the value is `null` just set it immediately.
+    if ( value === null ) {
+        if ( type == 'clear' ) type = 'select'
+        calendarItem[ type ] = value
+        return calendar
+    }
+
+    // Otherwise go through the queue of methods, and invoke the functions.
+    // Update this as the time unit, and set the final value as this item.
+    // * In the case of `enable`, keep the queue but set `disable` instead.
+    //   And in the case of `flip`, keep the queue but set `enable` instead.
+    calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) {
+        value = calendar[ method ]( type, value, options )
+        return value
+    }).pop()
+
+    // Check if we need to cascade through more updates.
+    if ( type == 'select' ) {
+        calendar.set( 'highlight', calendarItem.select, options )
+    }
+    else if ( type == 'highlight' ) {
+        calendar.set( 'view', calendarItem.highlight, options )
+    }
+    else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) {
+        if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) {
+            calendar.set( 'select', calendarItem.select, options )
+        }
+        if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) {
+            calendar.set( 'highlight', calendarItem.highlight, options )
+        }
+    }
+
+    return calendar
+} //DatePicker.prototype.set
+
+
+/**
+ * Get a datepicker item object.
+ */
+DatePicker.prototype.get = function( type ) {
+    return this.item[ type ]
+} //DatePicker.prototype.get
+
+
+/**
+ * Create a picker date object.
+ */
+DatePicker.prototype.create = function( type, value, options ) {
+
+    var isInfiniteValue,
+        calendar = this
+
+    // If there’s no value, use the type as the value.
+    value = value === undefined ? type : value
+
+
+    // If it’s infinity, update the value.
+    if ( value == -Infinity || value == Infinity ) {
+        isInfiniteValue = value
+    }
+
+    // If it’s an object, use the native date object.
+    else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
+        value = value.obj
+    }
+
+    // If it’s an array, convert it into a date and make sure
+    // that it’s a valid date – otherwise default to today.
+    else if ( $.isArray( value ) ) {
+        value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] )
+        value = _.isDate( value ) ? value : calendar.create().obj
+    }
+
+    // If it’s a number or date object, make a normalized date.
+    else if ( _.isInteger( value ) || _.isDate( value ) ) {
+        value = calendar.normalize( new Date( value ), options )
+    }
+
+    // If it’s a literal true or any other case, set it to now.
+    else /*if ( value === true )*/ {
+        value = calendar.now( type, value, options )
+    }
+
+    // Return the compiled object.
+    return {
+        year: isInfiniteValue || value.getFullYear(),
+        month: isInfiniteValue || value.getMonth(),
+        date: isInfiniteValue || value.getDate(),
+        day: isInfiniteValue || value.getDay(),
+        obj: isInfiniteValue || value,
+        pick: isInfiniteValue || value.getTime()
+    }
+} //DatePicker.prototype.create
+
+
+/**
+ * Create a range limit object using an array, date object,
+ * literal “true”, or integer relative to another time.
+ */
+DatePicker.prototype.createRange = function( from, to ) {
+
+    var calendar = this,
+        createDate = function( date ) {
+            if ( date === true || $.isArray( date ) || _.isDate( date ) ) {
+                return calendar.create( date )
+            }
+            return date
+        }
+
+    // Create objects if possible.
+    if ( !_.isInteger( from ) ) {
+        from = createDate( from )
+    }
+    if ( !_.isInteger( to ) ) {
+        to = createDate( to )
+    }
+
+    // Create relative dates.
+    if ( _.isInteger( from ) && $.isPlainObject( to ) ) {
+        from = [ to.year, to.month, to.date + from ];
+    }
+    else if ( _.isInteger( to ) && $.isPlainObject( from ) ) {
+        to = [ from.year, from.month, from.date + to ];
+    }
+
+    return {
+        from: createDate( from ),
+        to: createDate( to )
+    }
+} //DatePicker.prototype.createRange
+
+
+/**
+ * Check if a date unit falls within a date range object.
+ */
+DatePicker.prototype.withinRange = function( range, dateUnit ) {
+    range = this.createRange(range.from, range.to)
+    return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick
+}
+
+
+/**
+ * Check if two date range objects overlap.
+ */
+DatePicker.prototype.overlapRanges = function( one, two ) {
+
+    var calendar = this
+
+    // Convert the ranges into comparable dates.
+    one = calendar.createRange( one.from, one.to )
+    two = calendar.createRange( two.from, two.to )
+
+    return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) ||
+        calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to )
+}
+
+
+/**
+ * Get the date today.
+ */
+DatePicker.prototype.now = function( type, value, options ) {
+    value = new Date()
+    if ( options && options.rel ) {
+        value.setDate( value.getDate() + options.rel )
+    }
+    return this.normalize( value, options )
+}
+
+
+/**
+ * Navigate to next/prev month.
+ */
+DatePicker.prototype.navigate = function( type, value, options ) {
+
+    var targetDateObject,
+        targetYear,
+        targetMonth,
+        targetDate,
+        isTargetArray = $.isArray( value ),
+        isTargetObject = $.isPlainObject( value ),
+        viewsetObject = this.item.view/*,
+        safety = 100*/
+
+
+    if ( isTargetArray || isTargetObject ) {
+
+        if ( isTargetObject ) {
+            targetYear = value.year
+            targetMonth = value.month
+            targetDate = value.date
+        }
+        else {
+            targetYear = +value[0]
+            targetMonth = +value[1]
+            targetDate = +value[2]
+        }
+
+        // If we’re navigating months but the view is in a different
+        // month, navigate to the view’s year and month.
+        if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) {
+            targetYear = viewsetObject.year
+            targetMonth = viewsetObject.month
+        }
+
+        // Figure out the expected target year and month.
+        targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 )
+        targetYear = targetDateObject.getFullYear()
+        targetMonth = targetDateObject.getMonth()
+
+        // If the month we’re going to doesn’t have enough days,
+        // keep decreasing the date until we reach the month’s last date.
+        while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) {
+            targetDate -= 1
+            /*safety -= 1
+            if ( !safety ) {
+                throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.'
+            }*/
+        }
+
+        value = [ targetYear, targetMonth, targetDate ]
+    }
+
+    return value
+} //DatePicker.prototype.navigate
+
+
+/**
+ * Normalize a date by setting the hours to midnight.
+ */
+DatePicker.prototype.normalize = function( value/*, options*/ ) {
+    value.setHours( 0, 0, 0, 0 )
+    return value
+}
+
+
+/**
+ * Measure the range of dates.
+ */
+DatePicker.prototype.measure = function( type, value/*, options*/ ) {
+
+    var calendar = this
+
+    // If it’s anything false-y, remove the limits.
+    if ( !value ) {
+        value = type == 'min' ? -Infinity : Infinity
+    }
+
+    // If it’s a string, parse it.
+    else if ( typeof value == 'string' ) {
+        value = calendar.parse( type, value )
+    }
+
+    // If it's an integer, get a date relative to today.
+    else if ( _.isInteger( value ) ) {
+        value = calendar.now( type, value, { rel: value } )
+    }
+
+    return value
+} ///DatePicker.prototype.measure
+
+
+/**
+ * Create a viewset object based on navigation.
+ */
+DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) {
+    return this.create([ dateObject.year, dateObject.month, 1 ])
+}
+
+
+/**
+ * Validate a date as enabled and shift if needed.
+ */
+DatePicker.prototype.validate = function( type, dateObject, options ) {
+
+    var calendar = this,
+
+        // Keep a reference to the original date.
+        originalDateObject = dateObject,
+
+        // Make sure we have an interval.
+        interval = options && options.interval ? options.interval : 1,
+
+        // Check if the calendar enabled dates are inverted.
+        isFlippedBase = calendar.item.enable === -1,
+
+        // Check if we have any enabled dates after/before now.
+        hasEnabledBeforeTarget, hasEnabledAfterTarget,
+
+        // The min & max limits.
+        minLimitObject = calendar.item.min,
+        maxLimitObject = calendar.item.max,
+
+        // Check if we’ve reached the limit during shifting.
+        reachedMin, reachedMax,
+
+        // Check if the calendar is inverted and at least one weekday is enabled.
+        hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) {
+
+            // If there’s a date, check where it is relative to the target.
+            if ( $.isArray( value ) ) {
+                var dateTime = calendar.create( value ).pick
+                if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true
+                else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true
+            }
+
+            // Return only integers for enabled weekdays.
+            return _.isInteger( value )
+        }).length/*,
+
+        safety = 100*/
+
+
+
+    // Cases to validate for:
+    // [1] Not inverted and date disabled.
+    // [2] Inverted and some dates enabled.
+    // [3] Not inverted and out of range.
+    //
+    // Cases to **not** validate for:
+    // • Navigating months.
+    // • Not inverted and date enabled.
+    // • Inverted and all dates disabled.
+    // • ..and anything else.
+    if ( !options || !options.nav ) if (
+        /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) ||
+        /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) ||
+        /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) )
+    ) {
+
+
+        // When inverted, flip the direction if there aren’t any enabled weekdays
+        // and there are no enabled dates in the direction of the interval.
+        if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) {
+            interval *= -1
+        }
+
+
+        // Keep looping until we reach an enabled date.
+        while ( /*safety &&*/ calendar.disabled( dateObject ) ) {
+
+            /*safety -= 1
+            if ( !safety ) {
+                throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.'
+            }*/
+
+
+            // If we’ve looped into the next/prev month with a large interval, return to the original date and flatten the interval.
+            if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) {
+                dateObject = originalDateObject
+                interval = interval > 0 ? 1 : -1
+            }
+
+
+            // If we’ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit.
+            if ( dateObject.pick <= minLimitObject.pick ) {
+                reachedMin = true
+                interval = 1
+                dateObject = calendar.create([
+                    minLimitObject.year,
+                    minLimitObject.month,
+                    minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1)
+                ])
+            }
+            else if ( dateObject.pick >= maxLimitObject.pick ) {
+                reachedMax = true
+                interval = -1
+                dateObject = calendar.create([
+                    maxLimitObject.year,
+                    maxLimitObject.month,
+                    maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1)
+                ])
+            }
+
+
+            // If we’ve reached both limits, just break out of the loop.
+            if ( reachedMin && reachedMax ) {
+                break
+            }
+
+
+            // Finally, create the shifted date using the interval and keep looping.
+            dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ])
+        }
+
+    } //endif
+
+
+    // Return the date object settled on.
+    return dateObject
+} //DatePicker.prototype.validate
+
+
+/**
+ * Check if a date is disabled.
+ */
+DatePicker.prototype.disabled = function( dateToVerify ) {
+
+    var
+        calendar = this,
+
+        // Filter through the disabled dates to check if this is one.
+        isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) {
+
+            // If the date is a number, match the weekday with 0index and `firstDay` check.
+            if ( _.isInteger( dateToDisable ) ) {
+                return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7
+            }
+
+            // If it’s an array or a native JS date, create and match the exact date.
+            if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) {
+                return dateToVerify.pick === calendar.create( dateToDisable ).pick
+            }
+
+            // If it’s an object, match a date within the “from” and “to” range.
+            if ( $.isPlainObject( dateToDisable ) ) {
+                return calendar.withinRange( dateToDisable, dateToVerify )
+            }
+        })
+
+    // If this date matches a disabled date, confirm it’s not inverted.
+    isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) {
+        return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' ||
+            $.isPlainObject( dateToDisable ) && dateToDisable.inverted
+    }).length
+
+    // Check the calendar “enabled” flag and respectively flip the
+    // disabled state. Then also check if it’s beyond the min/max limits.
+    return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch ||
+        dateToVerify.pick < calendar.item.min.pick ||
+        dateToVerify.pick > calendar.item.max.pick
+
+} //DatePicker.prototype.disabled
+
+
+/**
+ * Parse a string into a usable type.
+ */
+DatePicker.prototype.parse = function( type, value, options ) {
+
+    var calendar = this,
+        parsingObject = {}
+
+    // If it’s already parsed, we’re good.
+    if ( !value || typeof value != 'string' ) {
+        return value
+    }
+
+    // We need a `.format` to parse the value with.
+    if ( !( options && options.format ) ) {
+        options = options || {}
+        options.format = calendar.settings.format
+    }
+
+    // Convert the format into an array and then map through it.
+    calendar.formats.toArray( options.format ).map( function( label ) {
+
+        var
+            // Grab the formatting label.
+            formattingLabel = calendar.formats[ label ],
+
+            // The format length is from the formatting label function or the
+            // label length without the escaping exclamation (!) mark.
+            formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length
+
+        // If there's a format label, split the value up to the format length.
+        // Then add it to the parsing object with appropriate label.
+        if ( formattingLabel ) {
+            parsingObject[ label ] = value.substr( 0, formatLength )
+        }
+
+        // Update the value as the substring from format length to end.
+        value = value.substr( formatLength )
+    })
+
+    // Compensate for month 0index.
+    return [
+        parsingObject.yyyy || parsingObject.yy,
+        +( parsingObject.mm || parsingObject.m ) - 1,
+        parsingObject.dd || parsingObject.d
+    ]
+} //DatePicker.prototype.parse
+
+
+/**
+ * Various formats to display the object in.
+ */
+DatePicker.prototype.formats = (function() {
+
+    // Return the length of the first word in a collection.
+    function getWordLengthFromCollection( string, collection, dateObject ) {
+
+        // Grab the first word from the string.
+        var word = string.match( /\w+/ )[ 0 ]
+
+        // If there's no month index, add it to the date object
+        if ( !dateObject.mm && !dateObject.m ) {
+            dateObject.m = collection.indexOf( word ) + 1
+        }
+
+        // Return the length of the word.
+        return word.length
+    }
+
+    // Get the length of the first word in a string.
+    function getFirstWordLength( string ) {
+        return string.match( /\w+/ )[ 0 ].length
+    }
+
+    return {
+
+        d: function( string, dateObject ) {
+
+            // If there's string, then get the digits length.
+            // Otherwise return the selected date.
+            return string ? _.digits( string ) : dateObject.date
+        },
+        dd: function( string, dateObject ) {
+
+            // If there's a string, then the length is always 2.
+            // Otherwise return the selected date with a leading zero.
+            return string ? 2 : _.lead( dateObject.date )
+        },
+        ddd: function( string, dateObject ) {
+
+            // If there's a string, then get the length of the first word.
+            // Otherwise return the short selected weekday.
+            return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ]
+        },
+        dddd: function( string, dateObject ) {
+
+            // If there's a string, then get the length of the first word.
+            // Otherwise return the full selected weekday.
+            return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ]
+        },
+        m: function( string, dateObject ) {
+
+            // If there's a string, then get the length of the digits
+            // Otherwise return the selected month with 0index compensation.
+            return string ? _.digits( string ) : dateObject.month + 1
+        },
+        mm: function( string, dateObject ) {
+
+            // If there's a string, then the length is always 2.
+            // Otherwise return the selected month with 0index and leading zero.
+            return string ? 2 : _.lead( dateObject.month + 1 )
+        },
+        mmm: function( string, dateObject ) {
+
+            var collection = this.settings.monthsShort
+
+            // If there's a string, get length of the relevant month from the short
+            // months collection. Otherwise return the selected month from that collection.
+            return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ]
+        },
+        mmmm: function( string, dateObject ) {
+
+            var collection = this.settings.monthsFull
+
+            // If there's a string, get length of the relevant month from the full
+            // months collection. Otherwise return the selected month from that collection.
+            return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ]
+        },
+        yy: function( string, dateObject ) {
+
+            // If there's a string, then the length is always 2.
+            // Otherwise return the selected year by slicing out the first 2 digits.
+            return string ? 2 : ( '' + dateObject.year ).slice( 2 )
+        },
+        yyyy: function( string, dateObject ) {
+
+            // If there's a string, then the length is always 4.
+            // Otherwise return the selected year.
+            return string ? 4 : dateObject.year
+        },
+
+        // Create an array by splitting the formatting string passed.
+        toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) },
+
+        // Format an object into a string using the formatting options.
+        toString: function ( formatString, itemObject ) {
+            var calendar = this
+            return calendar.formats.toArray( formatString ).map( function( label ) {
+                return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' )
+            }).join( '' )
+        }
+    }
+})() //DatePicker.prototype.formats
+
+
+
+
+/**
+ * Check if two date units are the exact.
+ */
+DatePicker.prototype.isDateExact = function( one, two ) {
+
+    var calendar = this
+
+    // When we’re working with weekdays, do a direct comparison.
+    if (
+        ( _.isInteger( one ) && _.isInteger( two ) ) ||
+        ( typeof one == 'boolean' && typeof two == 'boolean' )
+     ) {
+        return one === two
+    }
+
+    // When we’re working with date representations, compare the “pick” value.
+    if (
+        ( _.isDate( one ) || $.isArray( one ) ) &&
+        ( _.isDate( two ) || $.isArray( two ) )
+    ) {
+        return calendar.create( one ).pick === calendar.create( two ).pick
+    }
+
+    // When we’re working with range objects, compare the “from” and “to”.
+    if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+        return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to )
+    }
+
+    return false
+}
+
+
+/**
+ * Check if two date units overlap.
+ */
+DatePicker.prototype.isDateOverlap = function( one, two ) {
+
+    var calendar = this,
+        firstDay = calendar.settings.firstDay ? 1 : 0
+
+    // When we’re working with a weekday index, compare the days.
+    if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) {
+        one = one % 7 + firstDay
+        return one === calendar.create( two ).day + 1
+    }
+    if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) {
+        two = two % 7 + firstDay
+        return two === calendar.create( one ).day + 1
+    }
+
+    // When we’re working with range objects, check if the ranges overlap.
+    if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+        return calendar.overlapRanges( one, two )
+    }
+
+    return false
+}
+
+
+/**
+ * Flip the “enabled” state.
+ */
+DatePicker.prototype.flipEnable = function(val) {
+    var itemObject = this.item
+    itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
+}
+
+
+/**
+ * Mark a collection of dates as “disabled”.
+ */
+DatePicker.prototype.deactivate = function( type, datesToDisable ) {
+
+    var calendar = this,
+        disabledItems = calendar.item.disable.slice(0)
+
+
+    // If we’re flipping, that’s all we need to do.
+    if ( datesToDisable == 'flip' ) {
+        calendar.flipEnable()
+    }
+
+    else if ( datesToDisable === false ) {
+        calendar.flipEnable(1)
+        disabledItems = []
+    }
+
+    else if ( datesToDisable === true ) {
+        calendar.flipEnable(-1)
+        disabledItems = []
+    }
+
+    // Otherwise go through the dates to disable.
+    else {
+
+        datesToDisable.map(function( unitToDisable ) {
+
+            var matchFound
+
+            // When we have disabled items, check for matches.
+            // If something is matched, immediately break out.
+            for ( var index = 0; index < disabledItems.length; index += 1 ) {
+                if ( calendar.isDateExact( unitToDisable, disabledItems[index] ) ) {
+                    matchFound = true
+                    break
+                }
+            }
+
+            // If nothing was found, add the validated unit to the collection.
+            if ( !matchFound ) {
+                if (
+                    _.isInteger( unitToDisable ) ||
+                    _.isDate( unitToDisable ) ||
+                    $.isArray( unitToDisable ) ||
+                    ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to )
+                ) {
+                    disabledItems.push( unitToDisable )
+                }
+            }
+        })
+    }
+
+    // Return the updated collection.
+    return disabledItems
+} //DatePicker.prototype.deactivate
+
+
+/**
+ * Mark a collection of dates as “enabled”.
+ */
+DatePicker.prototype.activate = function( type, datesToEnable ) {
+
+    var calendar = this,
+        disabledItems = calendar.item.disable,
+        disabledItemsCount = disabledItems.length
+
+    // If we’re flipping, that’s all we need to do.
+    if ( datesToEnable == 'flip' ) {
+        calendar.flipEnable()
+    }
+
+    else if ( datesToEnable === true ) {
+        calendar.flipEnable(1)
+        disabledItems = []
+    }
+
+    else if ( datesToEnable === false ) {
+        calendar.flipEnable(-1)
+        disabledItems = []
+    }
+
+    // Otherwise go through the disabled dates.
+    else {
+
+        datesToEnable.map(function( unitToEnable ) {
+
+            var matchFound,
+                disabledUnit,
+                index,
+                isExactRange
+
+            // Go through the disabled items and try to find a match.
+            for ( index = 0; index < disabledItemsCount; index += 1 ) {
+
+                disabledUnit = disabledItems[index]
+
+                // When an exact match is found, remove it from the collection.
+                if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) {
+                    matchFound = disabledItems[index] = null
+                    isExactRange = true
+                    break
+                }
+
+                // When an overlapped match is found, add the “inverted” state to it.
+                else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) {
+                    if ( $.isPlainObject( unitToEnable ) ) {
+                        unitToEnable.inverted = true
+                        matchFound = unitToEnable
+                    }
+                    else if ( $.isArray( unitToEnable ) ) {
+                        matchFound = unitToEnable
+                        if ( !matchFound[3] ) matchFound.push( 'inverted' )
+                    }
+                    else if ( _.isDate( unitToEnable ) ) {
+                        matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ]
+                    }
+                    break
+                }
+            }
+
+            // If a match was found, remove a previous duplicate entry.
+            if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
+                if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) {
+                    disabledItems[index] = null
+                    break
+                }
+            }
+
+            // In the event that we’re dealing with an exact range of dates,
+            // make sure there are no “inverted” dates because of it.
+            if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
+                if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) {
+                    disabledItems[index] = null
+                    break
+                }
+            }
+
+            // If something is still matched, add it into the collection.
+            if ( matchFound ) {
+                disabledItems.push( matchFound )
+            }
+        })
+    }
+
+    // Return the updated collection.
+    return disabledItems.filter(function( val ) { return val != null })
+} //DatePicker.prototype.activate
+
+
+/**
+ * Create a string for the nodes in the picker.
+ */
+DatePicker.prototype.nodes = function( isOpen ) {
+
+    var
+        calendar = this,
+        settings = calendar.settings,
+        calendarItem = calendar.item,
+        nowObject = calendarItem.now,
+        selectedObject = calendarItem.select,
+        highlightedObject = calendarItem.highlight,
+        viewsetObject = calendarItem.view,
+        disabledCollection = calendarItem.disable,
+        minLimitObject = calendarItem.min,
+        maxLimitObject = calendarItem.max,
+
+
+        // Create the calendar table head using a copy of weekday labels collection.
+        // * We do a copy so we don't mutate the original array.
+        tableHead = (function( collection, fullCollection ) {
+
+            // If the first day should be Monday, move Sunday to the end.
+            if ( settings.firstDay ) {
+                collection.push( collection.shift() )
+                fullCollection.push( fullCollection.shift() )
+            }
+
+            // Create and return the table head group.
+            return _.node(
+                'thead',
+                _.node(
+                    'tr',
+                    _.group({
+                        min: 0,
+                        max: DAYS_IN_WEEK - 1,
+                        i: 1,
+                        node: 'th',
+                        item: function( counter ) {
+                            return [
+                                collection[ counter ],
+                                settings.klass.weekdays,
+                                'scope=col title="' + fullCollection[ counter ] + '"'
+                            ]
+                        }
+                    })
+                )
+            ) //endreturn
+
+        // Materialize modified
+        })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysLetter ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead
+
+
+        // Create the nav for next/prev month.
+        createMonthNav = function( next ) {
+
+            // Otherwise, return the created month tag.
+            return _.node(
+                'div',
+                ' ',
+                settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + (
+
+                    // If the focused month is outside the range, disabled the button.
+                    ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) ||
+                    ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ?
+                    ' ' + settings.klass.navDisabled : ''
+                ),
+                'data-nav=' + ( next || -1 ) + ' ' +
+                _.ariaAttr({
+                    role: 'button',
+                    controls: calendar.$node[0].id + '_table'
+                }) + ' ' +
+                'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"'
+            ) //endreturn
+        }, //createMonthNav
+
+
+        // Create the month label.
+        //Materialize modified
+        createMonthLabel = function(override) {
+
+            var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull
+
+             // Materialize modified
+            if (override == "short_months") {
+              monthsCollection = settings.monthsShort;
+            }
+
+            // If there are months to select, add a dropdown menu.
+            if ( settings.selectMonths  && override == undefined) {
+
+                return _.node( 'select',
+                    _.group({
+                        min: 0,
+                        max: 11,
+                        i: 1,
+                        node: 'option',
+                        item: function( loopedMonth ) {
+
+                            return [
+
+                                // The looped month and no classes.
+                                monthsCollection[ loopedMonth ], 0,
+
+                                // Set the value and selected index.
+                                'value=' + loopedMonth +
+                                ( viewsetObject.month == loopedMonth ? ' selected' : '' ) +
+                                (
+                                    (
+                                        ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) ||
+                                        ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month )
+                                    ) ?
+                                    ' disabled' : ''
+                                )
+                            ]
+                        }
+                    }),
+                    settings.klass.selectMonth + ' browser-default',
+                    ( isOpen ? '' : 'disabled' ) + ' ' +
+                    _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' +
+                    'title="' + settings.labelMonthSelect + '"'
+                )
+            }
+
+            // Materialize modified
+            if (override == "short_months")
+                if (selectedObject != null)
+                return _.node( 'div', monthsCollection[ selectedObject.month ] );
+                else return _.node( 'div', monthsCollection[ viewsetObject.month ] );
+
+            // If there's a need for a month selector
+            return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month )
+        }, //createMonthLabel
+
+
+        // Create the year label.
+        // Materialize modified
+        createYearLabel = function(override) {
+
+            var focusedYear = viewsetObject.year,
+
+            // If years selector is set to a literal "true", set it to 5. Otherwise
+            // divide in half to get half before and half after focused year.
+            numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 )
+
+            // If there are years to select, add a dropdown menu.
+            if ( numberYears ) {
+
+                var
+                    minYear = minLimitObject.year,
+                    maxYear = maxLimitObject.year,
+                    lowestYear = focusedYear - numberYears,
+                    highestYear = focusedYear + numberYears
+
+                // If the min year is greater than the lowest year, increase the highest year
+                // by the difference and set the lowest year to the min year.
+                if ( minYear > lowestYear ) {
+                    highestYear += minYear - lowestYear
+                    lowestYear = minYear
+                }
+
+                // If the max year is less than the highest year, decrease the lowest year
+                // by the lower of the two: available and needed years. Then set the
+                // highest year to the max year.
+                if ( maxYear < highestYear ) {
+
+                    var availableYears = lowestYear - minYear,
+                        neededYears = highestYear - maxYear
+
+                    lowestYear -= availableYears > neededYears ? neededYears : availableYears
+                    highestYear = maxYear
+                }
+
+                if ( settings.selectYears  && override == undefined ) {
+                    return _.node( 'select',
+                        _.group({
+                            min: lowestYear,
+                            max: highestYear,
+                            i: 1,
+                            node: 'option',
+                            item: function( loopedYear ) {
+                                return [
+
+                                    // The looped year and no classes.
+                                    loopedYear, 0,
+
+                                    // Set the value and selected index.
+                                    'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' )
+                                ]
+                            }
+                        }),
+                        settings.klass.selectYear + ' browser-default',
+                        ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' +
+                        'title="' + settings.labelYearSelect + '"'
+                    )
+                }
+            }
+
+            // Materialize modified
+            if (override == "raw")
+                return _.node( 'div', focusedYear )
+
+            // Otherwise just return the year focused
+            return _.node( 'div', focusedYear, settings.klass.year )
+        } //createYearLabel
+
+
+        // Materialize modified
+        createDayLabel = function() {
+                if (selectedObject != null)
+                    return _.node( 'div', selectedObject.date)
+                else return _.node( 'div', nowObject.date)
+            }
+        createWeekdayLabel = function() {
+            var display_day;
+
+            if (selectedObject != null)
+                display_day = selectedObject.day;
+            else
+                display_day = nowObject.day;
+            var weekday = settings.weekdaysFull[ display_day ]
+            return weekday
+        }
+
+
+    // Create and return the entire calendar.
+return _.node(
+        // Date presentation View
+        'div',
+            _.node(
+                'div',
+                createWeekdayLabel(),
+                "picker__weekday-display"
+            )+
+            _.node(
+                // Div for short Month
+                'div',
+                createMonthLabel("short_months"),
+                settings.klass.month_display
+            )+
+            _.node(
+                // Div for Day
+                'div',
+                createDayLabel() ,
+                settings.klass.day_display
+            )+
+            _.node(
+                // Div for Year
+                'div',
+                createYearLabel("raw") ,
+                settings.klass.year_display
+            ),
+        settings.klass.date_display
+    )+
+    // Calendar container
+    _.node('div',
+        _.node('div',
+        ( settings.selectYears ?  createMonthLabel() + createYearLabel() : createMonthLabel() + createYearLabel() ) +
+        createMonthNav() + createMonthNav( 1 ),
+        settings.klass.header
+    ) + _.node(
+        'table',
+        tableHead +
+        _.node(
+            'tbody',
+            _.group({
+                min: 0,
+                max: WEEKS_IN_CALENDAR - 1,
+                i: 1,
+                node: 'tr',
+                item: function( rowCounter ) {
+
+                    // If Monday is the first day and the month starts on Sunday, shift the date back a week.
+                    var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0
+
+                    return [
+                        _.group({
+                            min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index
+                            max: function() {
+                                return this.min + DAYS_IN_WEEK - 1
+                            },
+                            i: 1,
+                            node: 'td',
+                            item: function( targetDate ) {
+
+                                // Convert the time date from a relative date to a target date.
+                                targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ])
+
+                                var isSelected = selectedObject && selectedObject.pick == targetDate.pick,
+                                    isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick,
+                                    isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick,
+                                    formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] )
+
+                                return [
+                                    _.node(
+                                        'div',
+                                        targetDate.date,
+                                        (function( klasses ) {
+
+                                            // Add the `infocus` or `outfocus` classes based on month in view.
+                                            klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus )
+
+                                            // Add the `today` class if needed.
+                                            if ( nowObject.pick == targetDate.pick ) {
+                                                klasses.push( settings.klass.now )
+                                            }
+
+                                            // Add the `selected` class if something's selected and the time matches.
+                                            if ( isSelected ) {
+                                                klasses.push( settings.klass.selected )
+                                            }
+
+                                            // Add the `highlighted` class if something's highlighted and the time matches.
+                                            if ( isHighlighted ) {
+                                                klasses.push( settings.klass.highlighted )
+                                            }
+
+                                            // Add the `disabled` class if something's disabled and the object matches.
+                                            if ( isDisabled ) {
+                                                klasses.push( settings.klass.disabled )
+                                            }
+
+                                            return klasses.join( ' ' )
+                                        })([ settings.klass.day ]),
+                                        'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({
+                                            role: 'gridcell',
+                                            label: formattedDate,
+                                            selected: isSelected && calendar.$node.val() === formattedDate ? true : null,
+                                            activedescendant: isHighlighted ? true : null,
+                                            disabled: isDisabled ? true : null
+                                        })
+                                    ),
+                                    '',
+                                    _.ariaAttr({ role: 'presentation' })
+                                ] //endreturn
+                            }
+                        })
+                    ] //endreturn
+                }
+            })
+        ),
+        settings.klass.table,
+        'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({
+            role: 'grid',
+            controls: calendar.$node[0].id,
+            readonly: true
+        })
+    )
+    , settings.klass.calendar_container) // end calendar
+
+     +
+
+    // * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”.
+    _.node(
+        'div',
+        _.node( 'button', settings.today, "btn-flat picker__today",
+            'type=button data-pick=' + nowObject.pick +
+            ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' +
+            _.ariaAttr({ controls: calendar.$node[0].id }) ) +
+        _.node( 'button', settings.clear, "btn-flat picker__clear",
+            'type=button data-clear=1' +
+            ( isOpen ? '' : ' disabled' ) + ' ' +
+            _.ariaAttr({ controls: calendar.$node[0].id }) ) +
+        _.node('button', settings.close, "btn-flat picker__close",
+            'type=button data-close=true ' +
+            ( isOpen ? '' : ' disabled' ) + ' ' +
+            _.ariaAttr({ controls: calendar.$node[0].id }) ),
+        settings.klass.footer
+    ) //endreturn
+} //DatePicker.prototype.nodes
+
+
+
+
+/**
+ * The date picker defaults.
+ */
+DatePicker.defaults = (function( prefix ) {
+
+    return {
+
+        // The title label to use for the month nav buttons
+        labelMonthNext: 'Next month',
+        labelMonthPrev: 'Previous month',
+
+        // The title label to use for the dropdown selectors
+        labelMonthSelect: 'Select a month',
+        labelYearSelect: 'Select a year',
+
+        // Months and weekdays
+        monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
+        monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
+        weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
+        weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
+
+        // Materialize modified
+        weekdaysLetter: [ 'S', 'M', 'T', 'W', 'T', 'F', 'S' ],
+
+        // Today and clear
+        today: 'Today',
+        clear: 'Clear',
+        close: 'Close',
+
+        // The format to show on the `input` element
+        format: 'd mmmm, yyyy',
+
+        // Classes
+        klass: {
+
+            table: prefix + 'table',
+
+            header: prefix + 'header',
+
+
+            // Materialize Added klasses
+            date_display: prefix + 'date-display',
+            day_display: prefix + 'day-display',
+            month_display: prefix + 'month-display',
+            year_display: prefix + 'year-display',
+            calendar_container: prefix + 'calendar-container',
+            // end
+
+
+
+            navPrev: prefix + 'nav--prev',
+            navNext: prefix + 'nav--next',
+            navDisabled: prefix + 'nav--disabled',
+
+            month: prefix + 'month',
+            year: prefix + 'year',
+
+            selectMonth: prefix + 'select--month',
+            selectYear: prefix + 'select--year',
+
+            weekdays: prefix + 'weekday',
+
+            day: prefix + 'day',
+            disabled: prefix + 'day--disabled',
+            selected: prefix + 'day--selected',
+            highlighted: prefix + 'day--highlighted',
+            now: prefix + 'day--today',
+            infocus: prefix + 'day--infocus',
+            outfocus: prefix + 'day--outfocus',
+
+            footer: prefix + 'footer',
+
+            buttonClear: prefix + 'button--clear',
+            buttonToday: prefix + 'button--today',
+            buttonClose: prefix + 'button--close'
+        }
+    }
+})( Picker.klasses().picker + '__' )
+
+
+
+
+
+/**
+ * Extend the picker to add the date picker.
+ */
+Picker.extend( 'pickadate', DatePicker )
+
+
+}));
+
+
+;(function ($) {
+
+  $.fn.characterCounter = function(){
+    return this.each(function(){
+      var $input = $(this);
+      var $counterElement = $input.parent().find('span[class="character-counter"]');
+
+      // character counter has already been added appended to the parent container
+      if ($counterElement.length) {
+        return;
+      }
+
+      var itHasLengthAttribute = $input.attr('data-length') !== undefined;
+
+      if(itHasLengthAttribute){
+        $input.on('input', updateCounter);
+        $input.on('focus', updateCounter);
+        $input.on('blur', removeCounterElement);
+
+        addCounterElement($input);
+      }
+
+    });
+  };
+
+  function updateCounter(){
+    var maxLength     = +$(this).attr('data-length'),
+    actualLength      = +$(this).val().length,
+    isValidLength     = actualLength <= maxLength;
+
+    $(this).parent().find('span[class="character-counter"]')
+                    .html( actualLength + '/' + maxLength);
+
+    addInputStyle(isValidLength, $(this));
+  }
+
+  function addCounterElement($input) {
+    var $counterElement = $input.parent().find('span[class="character-counter"]');
+
+    if ($counterElement.length) {
+      return;
+    }
+
+    $counterElement = $('<span/>')
+                        .addClass('character-counter')
+                        .css('float','right')
+                        .css('font-size','12px')
+                        .css('height', 1);
+
+    $input.parent().append($counterElement);
+  }
+
+  function removeCounterElement(){
+    $(this).parent().find('span[class="character-counter"]').html('');
+  }
+
+  function addInputStyle(isValidLength, $input){
+    var inputHasInvalidClass = $input.hasClass('invalid');
+    if (isValidLength && inputHasInvalidClass) {
+      $input.removeClass('invalid');
+    }
+    else if(!isValidLength && !inputHasInvalidClass){
+      $input.removeClass('valid');
+      $input.addClass('invalid');
+    }
+  }
+
+  $(document).ready(function(){
+    $('input, textarea').characterCounter();
+  });
+
+}( jQuery ));
+;(function ($) {
+
+  var methods = {
+
+    init : function(options) {
+      var defaults = {
+        duration: 200, // ms
+        dist: -100, // zoom scale TODO: make this more intuitive as an option
+        shift: 0, // spacing for center image
+        padding: 0, // Padding between non center items
+        fullWidth: false, // Change to full width styles
+        indicators: false, // Toggle indicators
+        noWrap: false, // Don't wrap around and cycle through items.
+        onCycleTo: null // Callback for when a new slide is cycled to.
+      };
+      options = $.extend(defaults, options);
+
+      return this.each(function() {
+
+        var images, item_width, item_height, offset, center, pressed, dim, count,
+            reference, referenceY, amplitude, target, velocity,
+            xform, frame, timestamp, ticker, dragged, vertical_dragged;
+        var $indicators = $('<ul class="indicators"></ul>');
+
+
+        // Initialize
+        var view = $(this);
+        var showIndicators = view.attr('data-indicators') || options.indicators;
+
+        // Don't double initialize.
+        if (view.hasClass('initialized')) {
+          // Redraw carousel.
+          $(this).trigger('carouselNext', [0.000001]);
+          return true;
+        }
+
+
+        // Options
+        if (options.fullWidth) {
+          options.dist = 0;
+          var firstImage = view.find('.carousel-item img').first();
+          if (firstImage.length) {
+            imageHeight = firstImage.on('load', function(){
+              view.css('height', $(this).height());
+            });
+          } else {
+            imageHeight = view.find('.carousel-item').first().height();
+            view.css('height', imageHeight);
+          }
+
+          // Offset fixed items when indicators.
+          if (showIndicators) {
+            view.find('.carousel-fixed-item').addClass('with-indicators');
+          }
+        }
+
+
+        view.addClass('initialized');
+        pressed = false;
+        offset = target = 0;
+        images = [];
+        item_width = view.find('.carousel-item').first().innerWidth();
+        item_height = view.find('.carousel-item').first().innerHeight();
+        dim = item_width * 2 + options.padding;
+
+        view.find('.carousel-item').each(function (i) {
+          images.push($(this)[0]);
+          if (showIndicators) {
+            var $indicator = $('<li class="indicator-item"></li>');
+
+            // Add active to first by default.
+            if (i === 0) {
+              $indicator.addClass('active');
+            }
+
+            // Handle clicks on indicators.
+            $indicator.click(function (e) {
+              e.stopPropagation();
+
+              var index = $(this).index();
+              cycleTo(index);
+            });
+            $indicators.append($indicator);
+          }
+        });
+
+        if (showIndicators) {
+          view.append($indicators);
+        }
+        count = images.length;
+
+
+        function setupEvents() {
+          if (typeof window.ontouchstart !== 'undefined') {
+            view[0].addEventListener('touchstart', tap);
+            view[0].addEventListener('touchmove', drag);
+            view[0].addEventListener('touchend', release);
+          }
+          view[0].addEventListener('mousedown', tap);
+          view[0].addEventListener('mousemove', drag);
+          view[0].addEventListener('mouseup', release);
+          view[0].addEventListener('mouseleave', release);
+          view[0].addEventListener('click', click);
+        }
+
+        function xpos(e) {
+          // touch event
+          if (e.targetTouches && (e.targetTouches.length >= 1)) {
+            return e.targetTouches[0].clientX;
+          }
+
+          // mouse event
+          return e.clientX;
+        }
+
+        function ypos(e) {
+          // touch event
+          if (e.targetTouches && (e.targetTouches.length >= 1)) {
+            return e.targetTouches[0].clientY;
+          }
+
+          // mouse event
+          return e.clientY;
+        }
+
+        function wrap(x) {
+          return (x >= count) ? (x % count) : (x < 0) ? wrap(count + (x % count)) : x;
+        }
+
+        function scroll(x) {
+          var i, half, delta, dir, tween, el, alignment, xTranslation;
+          var lastCenter = center;
+
+          offset = (typeof x === 'number') ? x : offset;
+          center = Math.floor((offset + dim / 2) / dim);
+          delta = offset - center * dim;
+          dir = (delta < 0) ? 1 : -1;
+          tween = -dir * delta * 2 / dim;
+          half = count >> 1;
+
+          if (!options.fullWidth) {
+            alignment = 'translateX(' + (view[0].clientWidth - item_width) / 2 + 'px) ';
+            alignment += 'translateY(' + (view[0].clientHeight - item_height) / 2 + 'px)';
+          } else {
+            alignment = 'translateX(0)';
+          }
+
+          // Set indicator active
+          if (showIndicators) {
+            var diff = (center % count);
+            var activeIndicator = $indicators.find('.indicator-item.active');
+            if (activeIndicator.index() !== diff) {
+              activeIndicator.removeClass('active');
+              $indicators.find('.indicator-item').eq(diff).addClass('active');
+            }
+          }
+
+          // center
+          // Don't show wrapped items.
+          if (!options.noWrap || (center >= 0 && center < count)) {
+            el = images[wrap(center)];
+
+            // Add active class to center item.
+            if (!$(el).hasClass('active')) {
+              view.find('.carousel-item').removeClass('active');
+              $(el).addClass('active');
+            }
+            el.style[xform] = alignment +
+              ' translateX(' + (-delta / 2) + 'px)' +
+              ' translateX(' + (dir * options.shift * tween * i) + 'px)' +
+              ' translateZ(' + (options.dist * tween) + 'px)';
+            el.style.zIndex = 0;
+            if (options.fullWidth) { tweenedOpacity = 1; }
+            else { tweenedOpacity = 1 - 0.2 * tween; }
+            el.style.opacity = tweenedOpacity;
+            el.style.display = 'block';
+          }
+
+          for (i = 1; i <= half; ++i) {
+            // right side
+            if (options.fullWidth) {
+              zTranslation = options.dist;
+              tweenedOpacity = (i === half && delta < 0) ? 1 - tween : 1;
+            } else {
+              zTranslation = options.dist * (i * 2 + tween * dir);
+              tweenedOpacity = 1 - 0.2 * (i * 2 + tween * dir);
+            }
+            // Don't show wrapped items.
+            if (!options.noWrap || center + i < count) {
+              el = images[wrap(center + i)];
+              el.style[xform] = alignment +
+                ' translateX(' + (options.shift + (dim * i - delta) / 2) + 'px)' +
+                ' translateZ(' + zTranslation + 'px)';
+              el.style.zIndex = -i;
+              el.style.opacity = tweenedOpacity;
+              el.style.display = 'block';
+            }
+
+
+            // left side
+            if (options.fullWidth) {
+              zTranslation = options.dist;
+              tweenedOpacity = (i === half && delta > 0) ? 1 - tween : 1;
+            } else {
+              zTranslation = options.dist * (i * 2 - tween * dir);
+              tweenedOpacity = 1 - 0.2 * (i * 2 - tween * dir);
+            }
+            // Don't show wrapped items.
+            if (!options.noWrap || center - i >= 0) {
+              el = images[wrap(center - i)];
+              el.style[xform] = alignment +
+                ' translateX(' + (-options.shift + (-dim * i - delta) / 2) + 'px)' +
+                ' translateZ(' + zTranslation + 'px)';
+              el.style.zIndex = -i;
+              el.style.opacity = tweenedOpacity;
+              el.style.display = 'block';
+            }
+          }
+
+          // center
+          // Don't show wrapped items.
+          if (!options.noWrap || (center >= 0 && center < count)) {
+            el = images[wrap(center)];
+            el.style[xform] = alignment +
+              ' translateX(' + (-delta / 2) + 'px)' +
+              ' translateX(' + (dir * options.shift * tween) + 'px)' +
+              ' translateZ(' + (options.dist * tween) + 'px)';
+            el.style.zIndex = 0;
+            if (options.fullWidth) { tweenedOpacity = 1; }
+            else { tweenedOpacity = 1 - 0.2 * tween; }
+            el.style.opacity = tweenedOpacity;
+            el.style.display = 'block';
+          }
+
+          // onCycleTo callback
+          if (lastCenter !== center &&
+              typeof(options.onCycleTo) === "function") {
+            var $curr_item = view.find('.carousel-item').eq(wrap(center));
+            options.onCycleTo.call(this, $curr_item, dragged);
+          }
+        }
+
+        function track() {
+          var now, elapsed, delta, v;
+
+          now = Date.now();
+          elapsed = now - timestamp;
+          timestamp = now;
+          delta = offset - frame;
+          frame = offset;
+
+          v = 1000 * delta / (1 + elapsed);
+          velocity = 0.8 * v + 0.2 * velocity;
+        }
+
+        function autoScroll() {
+          var elapsed, delta;
+
+          if (amplitude) {
+            elapsed = Date.now() - timestamp;
+            delta = amplitude * Math.exp(-elapsed / options.duration);
+            if (delta > 2 || delta < -2) {
+                scroll(target - delta);
+                requestAnimationFrame(autoScroll);
+            } else {
+                scroll(target);
+            }
+          }
+        }
+
+        function click(e) {
+          // Disable clicks if carousel was dragged.
+          if (dragged) {
+            e.preventDefault();
+            e.stopPropagation();
+            return false;
+
+          } else if (!options.fullWidth) {
+            var clickedIndex = $(e.target).closest('.carousel-item').index();
+            var diff = (center % count) - clickedIndex;
+
+            // Disable clicks if carousel was shifted by click
+            if (diff !== 0) {
+              e.preventDefault();
+              e.stopPropagation();
+            }
+            cycleTo(clickedIndex);
+          }
+        }
+
+        function cycleTo(n) {
+          var diff = (center % count) - n;
+
+          // Account for wraparound.
+          if (!options.noWrap) {
+            if (diff < 0) {
+              if (Math.abs(diff + count) < Math.abs(diff)) { diff += count; }
+
+            } else if (diff > 0) {
+              if (Math.abs(diff - count) < diff) { diff -= count; }
+            }
+          }
+
+          // Call prev or next accordingly.
+          if (diff < 0) {
+            view.trigger('carouselNext', [Math.abs(diff)]);
+
+          } else if (diff > 0) {
+            view.trigger('carouselPrev', [diff]);
+          }
+        }
+
+        function tap(e) {
+          pressed = true;
+          dragged = false;
+          vertical_dragged = false;
+          reference = xpos(e);
+          referenceY = ypos(e);
+
+          velocity = amplitude = 0;
+          frame = offset;
+          timestamp = Date.now();
+          clearInterval(ticker);
+          ticker = setInterval(track, 100);
+
+        }
+
+        function drag(e) {
+          var x, delta, deltaY;
+          if (pressed) {
+            x = xpos(e);
+            y = ypos(e);
+            delta = reference - x;
+            deltaY = Math.abs(referenceY - y);
+            if (deltaY < 30 && !vertical_dragged) {
+              // If vertical scrolling don't allow dragging.
+              if (delta > 2 || delta < -2) {
+                dragged = true;
+                reference = x;
+                scroll(offset + delta);
+              }
+
+            } else if (dragged) {
+              // If dragging don't allow vertical scroll.
+              e.preventDefault();
+              e.stopPropagation();
+              return false;
+
+            } else {
+              // Vertical scrolling.
+              vertical_dragged = true;
+            }
+          }
+
+          if (dragged) {
+            // If dragging don't allow vertical scroll.
+            e.preventDefault();
+            e.stopPropagation();
+            return false;
+          }
+        }
+
+        function release(e) {
+          if (pressed) {
+            pressed = false;
+          } else {
+            return;
+          }
+
+          clearInterval(ticker);
+          target = offset;
+          if (velocity > 10 || velocity < -10) {
+            amplitude = 0.9 * velocity;
+            target = offset + amplitude;
+          }
+          target = Math.round(target / dim) * dim;
+
+          // No wrap of items.
+          if (options.noWrap) {
+            if (target >= dim * (count - 1)) {
+              target = dim * (count - 1);
+            } else if (target < 0) {
+              target = 0;
+            }
+          }
+          amplitude = target - offset;
+          timestamp = Date.now();
+          requestAnimationFrame(autoScroll);
+
+          if (dragged) {
+            e.preventDefault();
+            e.stopPropagation();
+          }
+          return false;
+        }
+
+        xform = 'transform';
+        ['webkit', 'Moz', 'O', 'ms'].every(function (prefix) {
+          var e = prefix + 'Transform';
+          if (typeof document.body.style[e] !== 'undefined') {
+            xform = e;
+            return false;
+          }
+          return true;
+        });
+
+
+        $(window).on('resize.carousel', function() {
+          if (options.fullWidth) {
+            item_width = view.find('.carousel-item').first().innerWidth();
+            item_height = view.find('.carousel-item').first().innerHeight();
+            dim = item_width * 2 + options.padding;
+            offset = center * 2 * item_width;
+            target = offset;
+          } else {
+            scroll();
+          }
+        });
+
+        setupEvents();
+        scroll(offset);
+
+        $(this).on('carouselNext', function(e, n) {
+          if (n === undefined) {
+            n = 1;
+          }
+          target = (dim * Math.round(offset / dim)) + (dim * n);
+          if (offset !== target) {
+            amplitude = target - offset;
+            timestamp = Date.now();
+            requestAnimationFrame(autoScroll);
+          }
+        });
+
+        $(this).on('carouselPrev', function(e, n) {
+          if (n === undefined) {
+            n = 1;
+          }
+          target = (dim * Math.round(offset / dim)) - (dim * n);
+          if (offset !== target) {
+            amplitude = target - offset;
+            timestamp = Date.now();
+            requestAnimationFrame(autoScroll);
+          }
+        });
+
+        $(this).on('carouselSet', function(e, n) {
+          if (n === undefined) {
+            n = 0;
+          }
+          cycleTo(n);
+        });
+
+      });
+
+
+
+    },
+    next : function(n) {
+      $(this).trigger('carouselNext', [n]);
+    },
+    prev : function(n) {
+      $(this).trigger('carouselPrev', [n]);
+    },
+    set : function(n) {
+      $(this).trigger('carouselSet', [n]);
+    }
+  };
+
+
+    $.fn.carousel = function(methodOrOptions) {
+      if ( methods[methodOrOptions] ) {
+        return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+      } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+        // Default to "init"
+        return methods.init.apply( this, arguments );
+      } else {
+        $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.carousel' );
+      }
+    }; // Plugin end
+}( jQuery ));
\ No newline at end of file
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/js/materialize.min.js b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/materialize/js/materialize.min.js
new file mode 100644 (file)
index 0000000..00c0d5f
--- /dev/null
@@ -0,0 +1,10 @@
+/*!
+ * Materialize v0.98.0 (http://materializecss.com)
+ * Copyright 2014-2015 Materialize
+ * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE)
+ */
+if("undefined"==typeof jQuery){var jQuery;jQuery="function"==typeof require?$=require("jquery"):$}jQuery.easing.jswing=jQuery.easing.swing,jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(a,b,c,d,e){return jQuery.easing[jQuery.easing.def](a,b,c,d,e)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b+c:-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b*b+c:d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return 0==b?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){return 0==b?c:b==e?c+d:(b/=e/2)<1?d/2*Math.pow(2,10*(b-1))+c:d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){return(b/=e/2)<1?-d/2*(Math.sqrt(1-b*b)-1)+c:d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(0==b)return c;if(1==(b/=e))return c+d;if(g||(g=.3*e),h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return-(h*Math.pow(2,10*(b-=1))*Math.sin((b*e-f)*(2*Math.PI)/g))+c},easeOutElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(0==b)return c;if(1==(b/=e))return c+d;if(g||(g=.3*e),h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*b)*Math.sin((b*e-f)*(2*Math.PI)/g)+d+c},easeInOutElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(0==b)return c;if(2==(b/=e/2))return c+d;if(g||(g=e*(.3*1.5)),h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return b<1?-.5*(h*Math.pow(2,10*(b-=1))*Math.sin((b*e-f)*(2*Math.PI)/g))+c:h*Math.pow(2,-10*(b-=1))*Math.sin((b*e-f)*(2*Math.PI)/g)*.5+d+c},easeInBack:function(a,b,c,d,e,f){return void 0==f&&(f=1.70158),d*(b/=e)*b*((f+1)*b-f)+c},easeOutBack:function(a,b,c,d,e,f){return void 0==f&&(f=1.70158),d*((b=b/e-1)*b*((f+1)*b+f)+1)+c},easeInOutBack:function(a,b,c,d,e,f){return void 0==f&&(f=1.70158),(b/=e/2)<1?d/2*(b*b*(((f*=1.525)+1)*b-f))+c:d/2*((b-=2)*b*(((f*=1.525)+1)*b+f)+2)+c},easeInBounce:function(a,b,c,d,e){return d-jQuery.easing.easeOutBounce(a,e-b,0,d,e)+c},easeOutBounce:function(a,b,c,d,e){return(b/=e)<1/2.75?d*(7.5625*b*b)+c:b<2/2.75?d*(7.5625*(b-=1.5/2.75)*b+.75)+c:b<2.5/2.75?d*(7.5625*(b-=2.25/2.75)*b+.9375)+c:d*(7.5625*(b-=2.625/2.75)*b+.984375)+c},easeInOutBounce:function(a,b,c,d,e){return b<e/2?.5*jQuery.easing.easeInBounce(a,2*b,0,d,e)+c:.5*jQuery.easing.easeOutBounce(a,2*b-e,0,d,e)+.5*d+c}}),jQuery.extend(jQuery.easing,{easeInOutMaterial:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b+c:d/4*((b-=2)*b*b+2)+c}}),jQuery.Velocity?console.log("Velocity is already loaded. You may be needlessly importing Velocity again; note that Materialize includes Velocity."):(!function(a){function b(a){var b=a.length,d=c.type(a);return"function"!==d&&!c.isWindow(a)&&(!(1!==a.nodeType||!b)||("array"===d||0===b||"number"==typeof b&&b>0&&b-1 in a))}if(!a.jQuery){var c=function(a,b){return new c.fn.init(a,b)};c.isWindow=function(a){return null!=a&&a==a.window},c.type=function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?e[g.call(a)]||"object":typeof a},c.isArray=Array.isArray||function(a){return"array"===c.type(a)},c.isPlainObject=function(a){var b;if(!a||"object"!==c.type(a)||a.nodeType||c.isWindow(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(d){return!1}for(b in a);return void 0===b||f.call(a,b)},c.each=function(a,c,d){var e,f=0,g=a.length,h=b(a);if(d){if(h)for(;g>f&&(e=c.apply(a[f],d),e!==!1);f++);else for(f in a)if(e=c.apply(a[f],d),e===!1)break}else if(h)for(;g>f&&(e=c.call(a[f],f,a[f]),e!==!1);f++);else for(f in a)if(e=c.call(a[f],f,a[f]),e===!1)break;return a},c.data=function(a,b,e){if(void 0===e){var f=a[c.expando],g=f&&d[f];if(void 0===b)return g;if(g&&b in g)return g[b]}else if(void 0!==b){var f=a[c.expando]||(a[c.expando]=++c.uuid);return d[f]=d[f]||{},d[f][b]=e,e}},c.removeData=function(a,b){var e=a[c.expando],f=e&&d[e];f&&c.each(b,function(a,b){delete f[b]})},c.extend=function(){var a,b,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;for("boolean"==typeof h&&(k=h,h=arguments[i]||{},i++),"object"!=typeof h&&"function"!==c.type(h)&&(h={}),i===j&&(h=this,i--);j>i;i++)if(null!=(f=arguments[i]))for(e in f)a=h[e],d=f[e],h!==d&&(k&&d&&(c.isPlainObject(d)||(b=c.isArray(d)))?(b?(b=!1,g=a&&c.isArray(a)?a:[]):g=a&&c.isPlainObject(a)?a:{},h[e]=c.extend(k,g,d)):void 0!==d&&(h[e]=d));return h},c.queue=function(a,d,e){function f(a,c){var d=c||[];return null!=a&&(b(Object(a))?!function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;)a[e++]=b[d++];if(c!==c)for(;void 0!==b[d];)a[e++]=b[d++];return a.length=e,a}(d,"string"==typeof a?[a]:a):[].push.call(d,a)),d}if(a){d=(d||"fx")+"queue";var g=c.data(a,d);return e?(!g||c.isArray(e)?g=c.data(a,d,f(e)):g.push(e),g):g||[]}},c.dequeue=function(a,b){c.each(a.nodeType?[a]:a,function(a,d){b=b||"fx";var e=c.queue(d,b),f=e.shift();"inprogress"===f&&(f=e.shift()),f&&("fx"===b&&e.unshift("inprogress"),f.call(d,function(){c.dequeue(d,b)}))})},c.fn=c.prototype={init:function(a){if(a.nodeType)return this[0]=a,this;throw new Error("Not a DOM node.")},offset:function(){var b=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:b.top+(a.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:b.left+(a.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){function a(){for(var a=this.offsetParent||document;a&&"html"===!a.nodeType.toLowerCase&&"static"===a.style.position;)a=a.offsetParent;return a||document}var b=this[0],a=a.apply(b),d=this.offset(),e=/^(?:body|html)$/i.test(a.nodeName)?{top:0,left:0}:c(a).offset();return d.top-=parseFloat(b.style.marginTop)||0,d.left-=parseFloat(b.style.marginLeft)||0,a.style&&(e.top+=parseFloat(a.style.borderTopWidth)||0,e.left+=parseFloat(a.style.borderLeftWidth)||0),{top:d.top-e.top,left:d.left-e.left}}};var d={};c.expando="velocity"+(new Date).getTime(),c.uuid=0;for(var e={},f=e.hasOwnProperty,g=e.toString,h="Boolean Number String Function Array Date RegExp Object Error".split(" "),i=0;i<h.length;i++)e["[object "+h[i]+"]"]=h[i].toLowerCase();c.fn.init.prototype=c.fn,a.Velocity={Utilities:c}}}(window),function(a){"object"==typeof module&&"object"==typeof module.exports?module.exports=a():"function"==typeof define&&define.amd?define(a):a()}(function(){return function(a,b,c,d){function e(a){for(var b=-1,c=a?a.length:0,d=[];++b<c;){var e=a[b];e&&d.push(e)}return d}function f(a){return p.isWrapped(a)?a=[].slice.call(a):p.isNode(a)&&(a=[a]),a}function g(a){var b=m.data(a,"velocity");return null===b?d:b}function h(a){return function(b){return Math.round(b*a)*(1/a)}}function i(a,c,d,e){function f(a,b){return 1-3*b+3*a}function g(a,b){return 3*b-6*a}function h(a){return 3*a}function i(a,b,c){return((f(b,c)*a+g(b,c))*a+h(b))*a}function j(a,b,c){return 3*f(b,c)*a*a+2*g(b,c)*a+h(b)}function k(b,c){for(var e=0;p>e;++e){var f=j(c,a,d);if(0===f)return c;var g=i(c,a,d)-b;c-=g/f}return c}function l(){for(var b=0;t>b;++b)x[b]=i(b*u,a,d)}function m(b,c,e){var f,g,h=0;do g=c+(e-c)/2,f=i(g,a,d)-b,f>0?e=g:c=g;while(Math.abs(f)>r&&++h<s);return g}function n(b){for(var c=0,e=1,f=t-1;e!=f&&x[e]<=b;++e)c+=u;--e;var g=(b-x[e])/(x[e+1]-x[e]),h=c+g*u,i=j(h,a,d);return i>=q?k(b,h):0==i?h:m(b,c,c+u)}function o(){y=!0,(a!=c||d!=e)&&l()}var p=4,q=.001,r=1e-7,s=10,t=11,u=1/(t-1),v="Float32Array"in b;if(4!==arguments.length)return!1;for(var w=0;4>w;++w)if("number"!=typeof arguments[w]||isNaN(arguments[w])||!isFinite(arguments[w]))return!1;a=Math.min(a,1),d=Math.min(d,1),a=Math.max(a,0),d=Math.max(d,0);var x=v?new Float32Array(t):new Array(t),y=!1,z=function(b){return y||o(),a===c&&d===e?b:0===b?0:1===b?1:i(n(b),c,e)};z.getControlPoints=function(){return[{x:a,y:c},{x:d,y:e}]};var A="generateBezier("+[a,c,d,e]+")";return z.toString=function(){return A},z}function j(a,b){var c=a;return p.isString(a)?t.Easings[a]||(c=!1):c=p.isArray(a)&&1===a.length?h.apply(null,a):p.isArray(a)&&2===a.length?u.apply(null,a.concat([b])):!(!p.isArray(a)||4!==a.length)&&i.apply(null,a),c===!1&&(c=t.Easings[t.defaults.easing]?t.defaults.easing:s),c}function k(a){if(a){var b=(new Date).getTime(),c=t.State.calls.length;c>1e4&&(t.State.calls=e(t.State.calls));for(var f=0;c>f;f++)if(t.State.calls[f]){var h=t.State.calls[f],i=h[0],j=h[2],n=h[3],o=!!n,q=null;n||(n=t.State.calls[f][3]=b-16);for(var r=Math.min((b-n)/j.duration,1),s=0,u=i.length;u>s;s++){var w=i[s],y=w.element;if(g(y)){var z=!1;if(j.display!==d&&null!==j.display&&"none"!==j.display){if("flex"===j.display){var A=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];m.each(A,function(a,b){v.setPropertyValue(y,"display",b)})}v.setPropertyValue(y,"display",j.display)}j.visibility!==d&&"hidden"!==j.visibility&&v.setPropertyValue(y,"visibility",j.visibility);for(var B in w)if("element"!==B){var C,D=w[B],E=p.isString(D.easing)?t.Easings[D.easing]:D.easing;if(1===r)C=D.endValue;else{var F=D.endValue-D.startValue;if(C=D.startValue+F*E(r,j,F),!o&&C===D.currentValue)continue}if(D.currentValue=C,"tween"===B)q=C;else{if(v.Hooks.registered[B]){var G=v.Hooks.getRoot(B),H=g(y).rootPropertyValueCache[G];H&&(D.rootPropertyValue=H)}var I=v.setPropertyValue(y,B,D.currentValue+(0===parseFloat(C)?"":D.unitType),D.rootPropertyValue,D.scrollData);v.Hooks.registered[B]&&(g(y).rootPropertyValueCache[G]=v.Normalizations.registered[G]?v.Normalizations.registered[G]("extract",null,I[1]):I[1]),"transform"===I[0]&&(z=!0)}}j.mobileHA&&g(y).transformCache.translate3d===d&&(g(y).transformCache.translate3d="(0px, 0px, 0px)",z=!0),z&&v.flushTransformCache(y)}}j.display!==d&&"none"!==j.display&&(t.State.calls[f][2].display=!1),j.visibility!==d&&"hidden"!==j.visibility&&(t.State.calls[f][2].visibility=!1),j.progress&&j.progress.call(h[1],h[1],r,Math.max(0,n+j.duration-b),n,q),1===r&&l(f)}}t.State.isTicking&&x(k)}function l(a,b){if(!t.State.calls[a])return!1;for(var c=t.State.calls[a][0],e=t.State.calls[a][1],f=t.State.calls[a][2],h=t.State.calls[a][4],i=!1,j=0,k=c.length;k>j;j++){var l=c[j].element;if(b||f.loop||("none"===f.display&&v.setPropertyValue(l,"display",f.display),"hidden"===f.visibility&&v.setPropertyValue(l,"visibility",f.visibility)),f.loop!==!0&&(m.queue(l)[1]===d||!/\.velocityQueueEntryFlag/i.test(m.queue(l)[1]))&&g(l)){g(l).isAnimating=!1,g(l).rootPropertyValueCache={};var n=!1;m.each(v.Lists.transforms3D,function(a,b){var c=/^scale/.test(b)?1:0,e=g(l).transformCache[b];g(l).transformCache[b]!==d&&new RegExp("^\\("+c+"[^.]").test(e)&&(n=!0,delete g(l).transformCache[b])}),f.mobileHA&&(n=!0,delete g(l).transformCache.translate3d),n&&v.flushTransformCache(l),v.Values.removeClass(l,"velocity-animating")}if(!b&&f.complete&&!f.loop&&j===k-1)try{f.complete.call(e,e)}catch(o){setTimeout(function(){throw o},1)}h&&f.loop!==!0&&h(e),g(l)&&f.loop===!0&&!b&&(m.each(g(l).tweensContainer,function(a,b){/^rotate/.test(a)&&360===parseFloat(b.endValue)&&(b.endValue=0,b.startValue=360),/^backgroundPosition/.test(a)&&100===parseFloat(b.endValue)&&"%"===b.unitType&&(b.endValue=0,b.startValue=100)}),t(l,"reverse",{loop:!0,delay:f.delay})),f.queue!==!1&&m.dequeue(l,f.queue)}t.State.calls[a]=!1;for(var p=0,q=t.State.calls.length;q>p;p++)if(t.State.calls[p]!==!1){i=!0;break}i===!1&&(t.State.isTicking=!1,delete t.State.calls,t.State.calls=[])}var m,n=function(){if(c.documentMode)return c.documentMode;for(var a=7;a>4;a--){var b=c.createElement("div");if(b.innerHTML="<!--[if IE "+a+"]><span></span><![endif]-->",b.getElementsByTagName("span").length)return b=null,a}return d}(),o=function(){var a=0;return b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||function(b){var c,d=(new Date).getTime();return c=Math.max(0,16-(d-a)),a=d+c,setTimeout(function(){b(d+c)},c)}}(),p={isString:function(a){return"string"==typeof a},isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)},isNode:function(a){return a&&a.nodeType},isNodeList:function(a){return"object"==typeof a&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(a))&&a.length!==d&&(0===a.length||"object"==typeof a[0]&&a[0].nodeType>0)},isWrapped:function(a){return a&&(a.jquery||b.Zepto&&b.Zepto.zepto.isZ(a))},isSVG:function(a){return b.SVGElement&&a instanceof b.SVGElement},isEmptyObject:function(a){for(var b in a)return!1;return!0}},q=!1;if(a.fn&&a.fn.jquery?(m=a,q=!0):m=b.Velocity.Utilities,8>=n&&!q)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(7>=n)return void(jQuery.fn.velocity=jQuery.fn.animate);var r=400,s="swing",t={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:b.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:c.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[]},CSS:{},Utilities:m,Redirects:{},Easings:{},Promise:b.Promise,defaults:{queue:"",duration:r,easing:s,begin:d,complete:d,progress:d,display:d,visibility:d,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0},init:function(a){m.data(a,"velocity",{isSVG:p.isSVG(a),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:2,patch:2},debug:!1};b.pageYOffset!==d?(t.State.scrollAnchor=b,t.State.scrollPropertyLeft="pageXOffset",t.State.scrollPropertyTop="pageYOffset"):(t.State.scrollAnchor=c.documentElement||c.body.parentNode||c.body,t.State.scrollPropertyLeft="scrollLeft",t.State.scrollPropertyTop="scrollTop");var u=function(){function a(a){return-a.tension*a.x-a.friction*a.v}function b(b,c,d){var e={x:b.x+d.dx*c,v:b.v+d.dv*c,tension:b.tension,friction:b.friction};return{dx:e.v,dv:a(e)}}function c(c,d){var e={dx:c.v,dv:a(c)},f=b(c,.5*d,e),g=b(c,.5*d,f),h=b(c,d,g),i=1/6*(e.dx+2*(f.dx+g.dx)+h.dx),j=1/6*(e.dv+2*(f.dv+g.dv)+h.dv);return c.x=c.x+i*d,c.v=c.v+j*d,c}return function d(a,b,e){var f,g,h,i={x:-1,v:0,tension:null,friction:null},j=[0],k=0,l=1e-4,m=.016;for(a=parseFloat(a)||500,b=parseFloat(b)||20,e=e||null,i.tension=a,i.friction=b,f=null!==e,f?(k=d(a,b),g=k/e*m):g=m;h=c(h||i,g),j.push(1+h.x),k+=16,Math.abs(h.x)>l&&Math.abs(h.v)>l;);return f?function(a){return j[a*(j.length-1)|0]}:k}}();t.Easings={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},spring:function(a){return 1-Math.cos(4.5*a*Math.PI)*Math.exp(6*-a)}},m.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(a,b){t.Easings[b[0]]=i.apply(null,b[1])});var v=t.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"]},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var a=0;a<v.Lists.colors.length;a++){var b="color"===v.Lists.colors[a]?"0 0 0 1":"255 255 255 1";v.Hooks.templates[v.Lists.colors[a]]=["Red Green Blue Alpha",b]}var c,d,e;if(n)for(c in v.Hooks.templates){d=v.Hooks.templates[c],e=d[0].split(" ");var f=d[1].match(v.RegEx.valueSplit);"Color"===e[0]&&(e.push(e.shift()),f.push(f.shift()),v.Hooks.templates[c]=[e.join(" "),f.join(" ")])}for(c in v.Hooks.templates){d=v.Hooks.templates[c],e=d[0].split(" ");for(var a in e){var g=c+e[a],h=a;v.Hooks.registered[g]=[c,h]}}},getRoot:function(a){var b=v.Hooks.registered[a];return b?b[0]:a},cleanRootPropertyValue:function(a,b){return v.RegEx.valueUnwrap.test(b)&&(b=b.match(v.RegEx.valueUnwrap)[1]),v.Values.isCSSNullValue(b)&&(b=v.Hooks.templates[a][1]),b},extractValue:function(a,b){var c=v.Hooks.registered[a];if(c){var d=c[0],e=c[1];return b=v.Hooks.cleanRootPropertyValue(d,b),b.toString().match(v.RegEx.valueSplit)[e]}return b},injectValue:function(a,b,c){var d=v.Hooks.registered[a];if(d){var e,f,g=d[0],h=d[1];return c=v.Hooks.cleanRootPropertyValue(g,c),e=c.toString().match(v.RegEx.valueSplit),e[h]=b,f=e.join(" ")}return c}},Normalizations:{registered:{clip:function(a,b,c){switch(a){case"name":return"clip";case"extract":var d;return v.RegEx.wrappedValueAlreadyExtracted.test(c)?d=c:(d=c.toString().match(v.RegEx.valueUnwrap),d=d?d[1].replace(/,(\s+)?/g," "):c),d;case"inject":return"rect("+c+")"}},blur:function(a,b,c){switch(a){case"name":return t.State.isFirefox?"filter":"-webkit-filter";case"extract":var d=parseFloat(c);if(!d&&0!==d){var e=c.toString().match(/blur\(([0-9]+[A-z]+)\)/i);d=e?e[1]:0}return d;case"inject":return parseFloat(c)?"blur("+c+")":"none"}},opacity:function(a,b,c){if(8>=n)switch(a){case"name":return"filter";case"extract":var d=c.toString().match(/alpha\(opacity=(.*)\)/i);return c=d?d[1]/100:1;case"inject":return b.style.zoom=1,parseFloat(c)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(c),10)+")"}else switch(a){case"name":return"opacity";case"extract":return c;case"inject":return c}}},register:function(){9>=n||t.State.isGingerbread||(v.Lists.transformsBase=v.Lists.transformsBase.concat(v.Lists.transforms3D));for(var a=0;a<v.Lists.transformsBase.length;a++)!function(){var b=v.Lists.transformsBase[a];v.Normalizations.registered[b]=function(a,c,e){switch(a){case"name":return"transform";case"extract":return g(c)===d||g(c).transformCache[b]===d?/^scale/i.test(b)?1:0:g(c).transformCache[b].replace(/[()]/g,"");case"inject":var f=!1;switch(b.substr(0,b.length-1)){case"translate":f=!/(%|px|em|rem|vw|vh|\d)$/i.test(e);break;case"scal":case"scale":t.State.isAndroid&&g(c).transformCache[b]===d&&1>e&&(e=1),f=!/(\d)$/i.test(e);break;case"skew":f=!/(deg|\d)$/i.test(e);break;case"rotate":f=!/(deg|\d)$/i.test(e)}return f||(g(c).transformCache[b]="("+e+")"),g(c).transformCache[b]}}}();for(var a=0;a<v.Lists.colors.length;a++)!function(){var b=v.Lists.colors[a];v.Normalizations.registered[b]=function(a,c,e){switch(a){case"name":return b;case"extract":var f;if(v.RegEx.wrappedValueAlreadyExtracted.test(e))f=e;else{var g,h={black:"rgb(0, 0, 0)",blue:"rgb(0, 0, 255)",gray:"rgb(128, 128, 128)",green:"rgb(0, 128, 0)",red:"rgb(255, 0, 0)",white:"rgb(255, 255, 255)"};/^[A-z]+$/i.test(e)?g=h[e]!==d?h[e]:h.black:v.RegEx.isHex.test(e)?g="rgb("+v.Values.hexToRgb(e).join(" ")+")":/^rgba?\(/i.test(e)||(g=h.black),f=(g||e).toString().match(v.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g," ")}return 8>=n||3!==f.split(" ").length||(f+=" 1"),f;case"inject":return 8>=n?4===e.split(" ").length&&(e=e.split(/\s+/).slice(0,3).join(" ")):3===e.split(" ").length&&(e+=" 1"),(8>=n?"rgb":"rgba")+"("+e.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")"}}}()}},Names:{camelCase:function(a){return a.replace(/-(\w)/g,function(a,b){return b.toUpperCase()})},SVGAttribute:function(a){var b="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(n||t.State.isAndroid&&!t.State.isChrome)&&(b+="|transform"),new RegExp("^("+b+")$","i").test(a)},prefixCheck:function(a){if(t.State.prefixMatches[a])return[t.State.prefixMatches[a],!0];for(var b=["","Webkit","Moz","ms","O"],c=0,d=b.length;d>c;c++){var e;if(e=0===c?a:b[c]+a.replace(/^\w/,function(a){return a.toUpperCase()}),p.isString(t.State.prefixElement.style[e]))return t.State.prefixMatches[a]=e,[e,!0]}return[a,!1]}},Values:{hexToRgb:function(a){var b,c=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,d=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;return a=a.replace(c,function(a,b,c,d){return b+b+c+c+d+d}),b=d.exec(a),b?[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]:[0,0,0]},isCSSNullValue:function(a){return 0==a||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(a)},getUnitType:function(a){return/^(rotate|skew)/i.test(a)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(a)?"":"px"},getDisplayType:function(a){var b=a&&a.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(b)?"inline":/^(li)$/i.test(b)?"list-item":/^(tr)$/i.test(b)?"table-row":/^(table)$/i.test(b)?"table":/^(tbody)$/i.test(b)?"table-row-group":"block"},addClass:function(a,b){a.classList?a.classList.add(b):a.className+=(a.className.length?" ":"")+b},removeClass:function(a,b){a.classList?a.classList.remove(b):a.className=a.className.toString().replace(new RegExp("(^|\\s)"+b.split(" ").join("|")+"(\\s|$)","gi")," ")}},getPropertyValue:function(a,c,e,f){function h(a,c){function e(){j&&v.setPropertyValue(a,"display","none")}var i=0;if(8>=n)i=m.css(a,c);else{var j=!1;if(/^(width|height)$/.test(c)&&0===v.getPropertyValue(a,"display")&&(j=!0,v.setPropertyValue(a,"display",v.Values.getDisplayType(a))),!f){if("height"===c&&"border-box"!==v.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var k=a.offsetHeight-(parseFloat(v.getPropertyValue(a,"borderTopWidth"))||0)-(parseFloat(v.getPropertyValue(a,"borderBottomWidth"))||0)-(parseFloat(v.getPropertyValue(a,"paddingTop"))||0)-(parseFloat(v.getPropertyValue(a,"paddingBottom"))||0);return e(),k}if("width"===c&&"border-box"!==v.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var l=a.offsetWidth-(parseFloat(v.getPropertyValue(a,"borderLeftWidth"))||0)-(parseFloat(v.getPropertyValue(a,"borderRightWidth"))||0)-(parseFloat(v.getPropertyValue(a,"paddingLeft"))||0)-(parseFloat(v.getPropertyValue(a,"paddingRight"))||0);return e(),l}}var o;o=g(a)===d?b.getComputedStyle(a,null):g(a).computedStyle?g(a).computedStyle:g(a).computedStyle=b.getComputedStyle(a,null),"borderColor"===c&&(c="borderTopColor"),i=9===n&&"filter"===c?o.getPropertyValue(c):o[c],(""===i||null===i)&&(i=a.style[c]),e()}if("auto"===i&&/^(top|right|bottom|left)$/i.test(c)){var p=h(a,"position");("fixed"===p||"absolute"===p&&/top|left/i.test(c))&&(i=m(a).position()[c]+"px")}return i}var i;if(v.Hooks.registered[c]){var j=c,k=v.Hooks.getRoot(j);e===d&&(e=v.getPropertyValue(a,v.Names.prefixCheck(k)[0])),v.Normalizations.registered[k]&&(e=v.Normalizations.registered[k]("extract",a,e)),i=v.Hooks.extractValue(j,e)}else if(v.Normalizations.registered[c]){var l,o;l=v.Normalizations.registered[c]("name",a),"transform"!==l&&(o=h(a,v.Names.prefixCheck(l)[0]),v.Values.isCSSNullValue(o)&&v.Hooks.templates[c]&&(o=v.Hooks.templates[c][1])),i=v.Normalizations.registered[c]("extract",a,o)}if(!/^[\d-]/.test(i))if(g(a)&&g(a).isSVG&&v.Names.SVGAttribute(c))if(/^(height|width)$/i.test(c))try{i=a.getBBox()[c]}catch(p){i=0}else i=a.getAttribute(c);else i=h(a,v.Names.prefixCheck(c)[0]);return v.Values.isCSSNullValue(i)&&(i=0),t.debug>=2&&console.log("Get "+c+": "+i),i},setPropertyValue:function(a,c,d,e,f){var h=c;if("scroll"===c)f.container?f.container["scroll"+f.direction]=d:"Left"===f.direction?b.scrollTo(d,f.alternateValue):b.scrollTo(f.alternateValue,d);else if(v.Normalizations.registered[c]&&"transform"===v.Normalizations.registered[c]("name",a))v.Normalizations.registered[c]("inject",a,d),h="transform",d=g(a).transformCache[c];else{if(v.Hooks.registered[c]){var i=c,j=v.Hooks.getRoot(c);e=e||v.getPropertyValue(a,j),d=v.Hooks.injectValue(i,d,e),c=j}if(v.Normalizations.registered[c]&&(d=v.Normalizations.registered[c]("inject",a,d),c=v.Normalizations.registered[c]("name",a)),h=v.Names.prefixCheck(c)[0],8>=n)try{a.style[h]=d}catch(k){t.debug&&console.log("Browser does not support ["+d+"] for ["+h+"]")}else g(a)&&g(a).isSVG&&v.Names.SVGAttribute(c)?a.setAttribute(c,d):a.style[h]=d;t.debug>=2&&console.log("Set "+c+" ("+h+"): "+d)}return[h,d]},flushTransformCache:function(a){function b(b){return parseFloat(v.getPropertyValue(a,b))}var c="";if((n||t.State.isAndroid&&!t.State.isChrome)&&g(a).isSVG){var d={translate:[b("translateX"),b("translateY")],skewX:[b("skewX")],skewY:[b("skewY")],scale:1!==b("scale")?[b("scale"),b("scale")]:[b("scaleX"),b("scaleY")],rotate:[b("rotateZ"),0,0]};m.each(g(a).transformCache,function(a){/^translate/i.test(a)?a="translate":/^scale/i.test(a)?a="scale":/^rotate/i.test(a)&&(a="rotate"),d[a]&&(c+=a+"("+d[a].join(" ")+") ",delete d[a])})}else{var e,f;m.each(g(a).transformCache,function(b){return e=g(a).transformCache[b],"transformPerspective"===b?(f=e,!0):(9===n&&"rotateZ"===b&&(b="rotate"),void(c+=b+e+" "))}),f&&(c="perspective"+f+" "+c)}v.setPropertyValue(a,"transform",c)}};v.Hooks.register(),v.Normalizations.register(),t.hook=function(a,b,c){var e=d;return a=f(a),m.each(a,function(a,f){if(g(f)===d&&t.init(f),c===d)e===d&&(e=t.CSS.getPropertyValue(f,b));else{var h=t.CSS.setPropertyValue(f,b,c);"transform"===h[0]&&t.CSS.flushTransformCache(f),e=h}}),e};var w=function(){function a(){return h?B.promise||null:i}function e(){function a(a){function l(a,b){var c=d,e=d,g=d;return p.isArray(a)?(c=a[0],!p.isArray(a[1])&&/^[\d-]/.test(a[1])||p.isFunction(a[1])||v.RegEx.isHex.test(a[1])?g=a[1]:(p.isString(a[1])&&!v.RegEx.isHex.test(a[1])||p.isArray(a[1]))&&(e=b?a[1]:j(a[1],h.duration),a[2]!==d&&(g=a[2]))):c=a,b||(e=e||h.easing),p.isFunction(c)&&(c=c.call(f,y,x)),p.isFunction(g)&&(g=g.call(f,y,x)),[c||0,e,g]}function n(a,b){var c,d;return d=(b||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(a){return c=a,""}),c||(c=v.Values.getUnitType(a)),[d,c]}function r(){var a={myParent:f.parentNode||c.body,position:v.getPropertyValue(f,"position"),fontSize:v.getPropertyValue(f,"fontSize")},d=a.position===I.lastPosition&&a.myParent===I.lastParent,e=a.fontSize===I.lastFontSize;I.lastParent=a.myParent,I.lastPosition=a.position,I.lastFontSize=a.fontSize;var h=100,i={};if(e&&d)i.emToPx=I.lastEmToPx,i.percentToPxWidth=I.lastPercentToPxWidth,i.percentToPxHeight=I.lastPercentToPxHeight;else{var j=g(f).isSVG?c.createElementNS("http://www.w3.org/2000/svg","rect"):c.createElement("div");t.init(j),a.myParent.appendChild(j),m.each(["overflow","overflowX","overflowY"],function(a,b){t.CSS.setPropertyValue(j,b,"hidden")}),t.CSS.setPropertyValue(j,"position",a.position),t.CSS.setPropertyValue(j,"fontSize",a.fontSize),t.CSS.setPropertyValue(j,"boxSizing","content-box"),m.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(a,b){t.CSS.setPropertyValue(j,b,h+"%")}),t.CSS.setPropertyValue(j,"paddingLeft",h+"em"),i.percentToPxWidth=I.lastPercentToPxWidth=(parseFloat(v.getPropertyValue(j,"width",null,!0))||1)/h,i.percentToPxHeight=I.lastPercentToPxHeight=(parseFloat(v.getPropertyValue(j,"height",null,!0))||1)/h,i.emToPx=I.lastEmToPx=(parseFloat(v.getPropertyValue(j,"paddingLeft"))||1)/h,a.myParent.removeChild(j)}return null===I.remToPx&&(I.remToPx=parseFloat(v.getPropertyValue(c.body,"fontSize"))||16),null===I.vwToPx&&(I.vwToPx=parseFloat(b.innerWidth)/100,I.vhToPx=parseFloat(b.innerHeight)/100),i.remToPx=I.remToPx,i.vwToPx=I.vwToPx,i.vhToPx=I.vhToPx,t.debug>=1&&console.log("Unit ratios: "+JSON.stringify(i),f),i}if(h.begin&&0===y)try{h.begin.call(o,o)}catch(u){setTimeout(function(){throw u},1)}if("scroll"===C){var w,z,A,D=/^x$/i.test(h.axis)?"Left":"Top",E=parseFloat(h.offset)||0;h.container?p.isWrapped(h.container)||p.isNode(h.container)?(h.container=h.container[0]||h.container,w=h.container["scroll"+D],A=w+m(f).position()[D.toLowerCase()]+E):h.container=null:(w=t.State.scrollAnchor[t.State["scrollProperty"+D]],z=t.State.scrollAnchor[t.State["scrollProperty"+("Left"===D?"Top":"Left")]],A=m(f).offset()[D.toLowerCase()]+E),i={scroll:{rootPropertyValue:!1,startValue:w,currentValue:w,endValue:A,unitType:"",easing:h.easing,scrollData:{container:h.container,direction:D,alternateValue:z}},element:f},t.debug&&console.log("tweensContainer (scroll): ",i.scroll,f)}else if("reverse"===C){if(!g(f).tweensContainer)return void m.dequeue(f,h.queue);"none"===g(f).opts.display&&(g(f).opts.display="auto"),"hidden"===g(f).opts.visibility&&(g(f).opts.visibility="visible"),g(f).opts.loop=!1,g(f).opts.begin=null,g(f).opts.complete=null,s.easing||delete h.easing,s.duration||delete h.duration,h=m.extend({},g(f).opts,h);var F=m.extend(!0,{},g(f).tweensContainer);for(var G in F)if("element"!==G){var H=F[G].startValue;F[G].startValue=F[G].currentValue=F[G].endValue,F[G].endValue=H,p.isEmptyObject(s)||(F[G].easing=h.easing),t.debug&&console.log("reverse tweensContainer ("+G+"): "+JSON.stringify(F[G]),f)}i=F}else if("start"===C){var F;g(f).tweensContainer&&g(f).isAnimating===!0&&(F=g(f).tweensContainer),m.each(q,function(a,b){if(RegExp("^"+v.Lists.colors.join("$|^")+"$").test(a)){var c=l(b,!0),e=c[0],f=c[1],g=c[2];if(v.RegEx.isHex.test(e)){for(var h=["Red","Green","Blue"],i=v.Values.hexToRgb(e),j=g?v.Values.hexToRgb(g):d,k=0;k<h.length;k++){var m=[i[k]];f&&m.push(f),j!==d&&m.push(j[k]),q[a+h[k]]=m}delete q[a]}}});for(var K in q){var L=l(q[K]),M=L[0],N=L[1],O=L[2];K=v.Names.camelCase(K);var P=v.Hooks.getRoot(K),Q=!1;if(g(f).isSVG||"tween"===P||v.Names.prefixCheck(P)[1]!==!1||v.Normalizations.registered[P]!==d){(h.display!==d&&null!==h.display&&"none"!==h.display||h.visibility!==d&&"hidden"!==h.visibility)&&/opacity|filter/.test(K)&&!O&&0!==M&&(O=0),h._cacheValues&&F&&F[K]?(O===d&&(O=F[K].endValue+F[K].unitType),Q=g(f).rootPropertyValueCache[P]):v.Hooks.registered[K]?O===d?(Q=v.getPropertyValue(f,P),O=v.getPropertyValue(f,K,Q)):Q=v.Hooks.templates[P][1]:O===d&&(O=v.getPropertyValue(f,K));var R,S,T,U=!1;if(R=n(K,O),O=R[0],T=R[1],R=n(K,M),M=R[0].replace(/^([+-\/*])=/,function(a,b){return U=b,""}),S=R[1],O=parseFloat(O)||0,M=parseFloat(M)||0,"%"===S&&(/^(fontSize|lineHeight)$/.test(K)?(M/=100,S="em"):/^scale/.test(K)?(M/=100,S=""):/(Red|Green|Blue)$/i.test(K)&&(M=M/100*255,S="")),/[\/*]/.test(U))S=T;else if(T!==S&&0!==O)if(0===M)S=T;else{e=e||r();var V=/margin|padding|left|right|width|text|word|letter/i.test(K)||/X$/.test(K)||"x"===K?"x":"y";
+switch(T){case"%":O*="x"===V?e.percentToPxWidth:e.percentToPxHeight;break;case"px":break;default:O*=e[T+"ToPx"]}switch(S){case"%":O*=1/("x"===V?e.percentToPxWidth:e.percentToPxHeight);break;case"px":break;default:O*=1/e[S+"ToPx"]}}switch(U){case"+":M=O+M;break;case"-":M=O-M;break;case"*":M=O*M;break;case"/":M=O/M}i[K]={rootPropertyValue:Q,startValue:O,currentValue:O,endValue:M,unitType:S,easing:N},t.debug&&console.log("tweensContainer ("+K+"): "+JSON.stringify(i[K]),f)}else t.debug&&console.log("Skipping ["+P+"] due to a lack of browser support.")}i.element=f}i.element&&(v.Values.addClass(f,"velocity-animating"),J.push(i),""===h.queue&&(g(f).tweensContainer=i,g(f).opts=h),g(f).isAnimating=!0,y===x-1?(t.State.calls.push([J,o,h,null,B.resolver]),t.State.isTicking===!1&&(t.State.isTicking=!0,k())):y++)}var e,f=this,h=m.extend({},t.defaults,s),i={};switch(g(f)===d&&t.init(f),parseFloat(h.delay)&&h.queue!==!1&&m.queue(f,h.queue,function(a){t.velocityQueueEntryFlag=!0,g(f).delayTimer={setTimeout:setTimeout(a,parseFloat(h.delay)),next:a}}),h.duration.toString().toLowerCase()){case"fast":h.duration=200;break;case"normal":h.duration=r;break;case"slow":h.duration=600;break;default:h.duration=parseFloat(h.duration)||1}t.mock!==!1&&(t.mock===!0?h.duration=h.delay=1:(h.duration*=parseFloat(t.mock)||1,h.delay*=parseFloat(t.mock)||1)),h.easing=j(h.easing,h.duration),h.begin&&!p.isFunction(h.begin)&&(h.begin=null),h.progress&&!p.isFunction(h.progress)&&(h.progress=null),h.complete&&!p.isFunction(h.complete)&&(h.complete=null),h.display!==d&&null!==h.display&&(h.display=h.display.toString().toLowerCase(),"auto"===h.display&&(h.display=t.CSS.Values.getDisplayType(f))),h.visibility!==d&&null!==h.visibility&&(h.visibility=h.visibility.toString().toLowerCase()),h.mobileHA=h.mobileHA&&t.State.isMobile&&!t.State.isGingerbread,h.queue===!1?h.delay?setTimeout(a,h.delay):a():m.queue(f,h.queue,function(b,c){return c===!0?(B.promise&&B.resolver(o),!0):(t.velocityQueueEntryFlag=!0,void a(b))}),""!==h.queue&&"fx"!==h.queue||"inprogress"===m.queue(f)[0]||m.dequeue(f)}var h,i,n,o,q,s,u=arguments[0]&&(arguments[0].p||m.isPlainObject(arguments[0].properties)&&!arguments[0].properties.names||p.isString(arguments[0].properties));if(p.isWrapped(this)?(h=!1,n=0,o=this,i=this):(h=!0,n=1,o=u?arguments[0].elements||arguments[0].e:arguments[0]),o=f(o)){u?(q=arguments[0].properties||arguments[0].p,s=arguments[0].options||arguments[0].o):(q=arguments[n],s=arguments[n+1]);var x=o.length,y=0;if(!/^(stop|finish)$/i.test(q)&&!m.isPlainObject(s)){var z=n+1;s={};for(var A=z;A<arguments.length;A++)p.isArray(arguments[A])||!/^(fast|normal|slow)$/i.test(arguments[A])&&!/^\d/.test(arguments[A])?p.isString(arguments[A])||p.isArray(arguments[A])?s.easing=arguments[A]:p.isFunction(arguments[A])&&(s.complete=arguments[A]):s.duration=arguments[A]}var B={promise:null,resolver:null,rejecter:null};h&&t.Promise&&(B.promise=new t.Promise(function(a,b){B.resolver=a,B.rejecter=b}));var C;switch(q){case"scroll":C="scroll";break;case"reverse":C="reverse";break;case"finish":case"stop":m.each(o,function(a,b){g(b)&&g(b).delayTimer&&(clearTimeout(g(b).delayTimer.setTimeout),g(b).delayTimer.next&&g(b).delayTimer.next(),delete g(b).delayTimer)});var D=[];return m.each(t.State.calls,function(a,b){b&&m.each(b[1],function(c,e){var f=s===d?"":s;return f!==!0&&b[2].queue!==f&&(s!==d||b[2].queue!==!1)||void m.each(o,function(c,d){d===e&&((s===!0||p.isString(s))&&(m.each(m.queue(d,p.isString(s)?s:""),function(a,b){p.isFunction(b)&&b(null,!0)}),m.queue(d,p.isString(s)?s:"",[])),"stop"===q?(g(d)&&g(d).tweensContainer&&f!==!1&&m.each(g(d).tweensContainer,function(a,b){b.endValue=b.currentValue}),D.push(a)):"finish"===q&&(b[2].duration=1))})})}),"stop"===q&&(m.each(D,function(a,b){l(b,!0)}),B.promise&&B.resolver(o)),a();default:if(!m.isPlainObject(q)||p.isEmptyObject(q)){if(p.isString(q)&&t.Redirects[q]){var E=m.extend({},s),F=E.duration,G=E.delay||0;return E.backwards===!0&&(o=m.extend(!0,[],o).reverse()),m.each(o,function(a,b){parseFloat(E.stagger)?E.delay=G+parseFloat(E.stagger)*a:p.isFunction(E.stagger)&&(E.delay=G+E.stagger.call(b,a,x)),E.drag&&(E.duration=parseFloat(F)||(/^(callout|transition)/.test(q)?1e3:r),E.duration=Math.max(E.duration*(E.backwards?1-a/x:(a+1)/x),.75*E.duration,200)),t.Redirects[q].call(b,b,E||{},a,x,o,B.promise?B:d)}),a()}var H="Velocity: First argument ("+q+") was not a property map, a known action, or a registered redirect. Aborting.";return B.promise?B.rejecter(new Error(H)):console.log(H),a()}C="start"}var I={lastParent:null,lastPosition:null,lastFontSize:null,lastPercentToPxWidth:null,lastPercentToPxHeight:null,lastEmToPx:null,remToPx:null,vwToPx:null,vhToPx:null},J=[];m.each(o,function(a,b){p.isNode(b)&&e.call(b)});var K,E=m.extend({},t.defaults,s);if(E.loop=parseInt(E.loop),K=2*E.loop-1,E.loop)for(var L=0;K>L;L++){var M={delay:E.delay,progress:E.progress};L===K-1&&(M.display=E.display,M.visibility=E.visibility,M.complete=E.complete),w(o,"reverse",M)}return a()}};t=m.extend(w,t),t.animate=w;var x=b.requestAnimationFrame||o;return t.State.isMobile||c.hidden===d||c.addEventListener("visibilitychange",function(){c.hidden?(x=function(a){return setTimeout(function(){a(!0)},16)},k()):x=b.requestAnimationFrame||o}),a.Velocity=t,a!==b&&(a.fn.velocity=w,a.fn.velocity.defaults=t.defaults),m.each(["Down","Up"],function(a,b){t.Redirects["slide"+b]=function(a,c,e,f,g,h){var i=m.extend({},c),j=i.begin,k=i.complete,l={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""},n={};i.display===d&&(i.display="Down"===b?"inline"===t.CSS.Values.getDisplayType(a)?"inline-block":"block":"none"),i.begin=function(){j&&j.call(g,g);for(var c in l){n[c]=a.style[c];var d=t.CSS.getPropertyValue(a,c);l[c]="Down"===b?[d,0]:[0,d]}n.overflow=a.style.overflow,a.style.overflow="hidden"},i.complete=function(){for(var b in n)a.style[b]=n[b];k&&k.call(g,g),h&&h.resolver(g)},t(a,l,i)}}),m.each(["In","Out"],function(a,b){t.Redirects["fade"+b]=function(a,c,e,f,g,h){var i=m.extend({},c),j={opacity:"In"===b?1:0},k=i.complete;i.complete=e!==f-1?i.begin=null:function(){k&&k.call(g,g),h&&h.resolver(g)},i.display===d&&(i.display="In"===b?"auto":"none"),t(this,j,i)}}),t}(window.jQuery||window.Zepto||window,window,document)})),!function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(k(a,c),b)}function f(a,b,c){return!!Array.isArray(a)&&(g(a,c[b],c),!0)}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e<a.length;)b.call(c,a[e],e,a),e++;else for(e in a)a.hasOwnProperty(e)&&b.call(c,a[e],e,a)}function h(a,b,c){for(var e=Object.keys(b),f=0;f<e.length;)(!c||c&&a[e[f]]===d)&&(a[e[f]]=b[e[f]]),f++;return a}function i(a,b){return h(a,b,!0)}function j(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&h(d,c)}function k(a,b){return function(){return a.apply(b,arguments)}}function l(a,b){return typeof a==ka?a.apply(b?b[0]||d:d,b):a}function m(a,b){return a===d?b:a}function n(a,b,c){g(r(b),function(b){a.addEventListener(b,c,!1)})}function o(a,b,c){g(r(b),function(b){a.removeEventListener(b,c,!1)})}function p(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function q(a,b){return a.indexOf(b)>-1}function r(a){return a.trim().split(/\s+/g)}function s(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;d<a.length;){if(c&&a[d][c]==b||!c&&a[d]===b)return d;d++}return-1}function t(a){return Array.prototype.slice.call(a,0)}function u(a,b,c){for(var d=[],e=[],f=0;f<a.length;){var g=b?a[f][b]:a[f];s(e,g)<0&&d.push(a[f]),e[f]=g,f++}return c&&(d=b?d.sort(function(a,c){return a[b]>c[b]}):d.sort()),d}function v(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g<ia.length;){if(c=ia[g],e=c?c+f:b,e in a)return e;g++}return d}function w(){return oa++}function x(a){var b=a.ownerDocument;return b.defaultView||b.parentWindow}function y(a,b){var c=this;this.manager=a,this.callback=b,this.element=a.element,this.target=a.options.inputTarget,this.domHandler=function(b){l(a.options.enable,[a])&&c.handler(b)},this.init()}function z(a){var b,c=a.options.inputClass;return new(b=c?c:ra?N:sa?Q:qa?S:M)(a,A)}function A(a,b,c){var d=c.pointers.length,e=c.changedPointers.length,f=b&ya&&0===d-e,g=b&(Aa|Ba)&&0===d-e;c.isFirst=!!f,c.isFinal=!!g,f&&(a.session={}),c.eventType=b,B(a,c),a.emit("hammer.input",c),a.recognize(c),a.session.prevInput=c}function B(a,b){var c=a.session,d=b.pointers,e=d.length;c.firstInput||(c.firstInput=E(b)),e>1&&!c.firstMultiple?c.firstMultiple=E(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=F(d);b.timeStamp=na(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=J(h,i),b.distance=I(h,i),C(c,b),b.offsetDirection=H(b.deltaX,b.deltaY),b.scale=g?L(g.pointers,d):1,b.rotation=g?K(g.pointers,d):0,D(c,b);var j=a.element;p(b.srcEvent.target,j)&&(j=b.srcEvent.target),b.target=j}function C(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};(b.eventType===ya||f.eventType===Aa)&&(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function D(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ba&&(i>xa||h.velocity===d)){var j=h.deltaX-b.deltaX,k=h.deltaY-b.deltaY,l=G(i,j,k);e=l.x,f=l.y,c=ma(l.x)>ma(l.y)?l.x:l.y,g=H(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function E(a){for(var b=[],c=0;c<a.pointers.length;)b[c]={clientX:la(a.pointers[c].clientX),clientY:la(a.pointers[c].clientY)},c++;return{timeStamp:na(),pointers:b,center:F(b),deltaX:a.deltaX,deltaY:a.deltaY}}function F(a){var b=a.length;if(1===b)return{x:la(a[0].clientX),y:la(a[0].clientY)};for(var c=0,d=0,e=0;b>e;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:la(c/b),y:la(d/b)}}function G(a,b,c){return{x:b/a||0,y:c/a||0}}function H(a,b){return a===b?Ca:ma(a)>=ma(b)?a>0?Da:Ea:b>0?Fa:Ga}function I(a,b,c){c||(c=Ka);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function J(a,b,c){c||(c=Ka);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function K(a,b){return J(b[1],b[0],La)-J(a[1],a[0],La)}function L(a,b){return I(b[0],b[1],La)/I(a[0],a[1],La)}function M(){this.evEl=Na,this.evWin=Oa,this.allow=!0,this.pressed=!1,y.apply(this,arguments)}function N(){this.evEl=Ra,this.evWin=Sa,y.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function O(){this.evTarget=Ua,this.evWin=Va,this.started=!1,y.apply(this,arguments)}function P(a,b){var c=t(a.touches),d=t(a.changedTouches);return b&(Aa|Ba)&&(c=u(c.concat(d),"identifier",!0)),[c,d]}function Q(){this.evTarget=Xa,this.targetIds={},y.apply(this,arguments)}function R(a,b){var c=t(a.touches),d=this.targetIds;if(b&(ya|za)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=t(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return p(a.target,i)}),b===ya)for(e=0;e<f.length;)d[f[e].identifier]=!0,e++;for(e=0;e<g.length;)d[g[e].identifier]&&h.push(g[e]),b&(Aa|Ba)&&delete d[g[e].identifier],e++;return h.length?[u(f.concat(h),"identifier",!0),h]:void 0}function S(){y.apply(this,arguments);var a=k(this.handler,this);this.touch=new Q(this.manager,a),this.mouse=new M(this.manager,a)}function T(a,b){this.manager=a,this.set(b)}function U(a){if(q(a,bb))return bb;var b=q(a,cb),c=q(a,db);return b&&c?cb+" "+db:b||c?b?cb:db:q(a,ab)?ab:_a}function V(a){this.id=w(),this.manager=null,this.options=i(a||{},this.defaults),this.options.enable=m(this.options.enable,!0),this.state=eb,this.simultaneous={},this.requireFail=[]}function W(a){return a&jb?"cancel":a&hb?"end":a&gb?"move":a&fb?"start":""}function X(a){return a==Ga?"down":a==Fa?"up":a==Da?"left":a==Ea?"right":""}function Y(a,b){var c=b.manager;return c?c.get(a):a}function Z(){V.apply(this,arguments)}function $(){Z.apply(this,arguments),this.pX=null,this.pY=null}function _(){Z.apply(this,arguments)}function aa(){V.apply(this,arguments),this._timer=null,this._input=null}function ba(){Z.apply(this,arguments)}function ca(){Z.apply(this,arguments)}function da(){V.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ea(a,b){return b=b||{},b.recognizers=m(b.recognizers,ea.defaults.preset),new fa(a,b)}function fa(a,b){b=b||{},this.options=i(b,ea.defaults),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.element=a,this.input=z(this),this.touchAction=new T(this,this.options.touchAction),ga(this,!0),g(b.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ga(a,b){var c=a.element;g(a.options.cssProps,function(a,d){c.style[v(c.style,d)]=b?a:""})}function ha(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var ia=["","webkit","moz","MS","ms","o"],ja=b.createElement("div"),ka="function",la=Math.round,ma=Math.abs,na=Date.now,oa=1,pa=/mobile|tablet|ip(ad|hone|od)|android/i,qa="ontouchstart"in a,ra=v(a,"PointerEvent")!==d,sa=qa&&pa.test(navigator.userAgent),ta="touch",ua="pen",va="mouse",wa="kinect",xa=25,ya=1,za=2,Aa=4,Ba=8,Ca=1,Da=2,Ea=4,Fa=8,Ga=16,Ha=Da|Ea,Ia=Fa|Ga,Ja=Ha|Ia,Ka=["x","y"],La=["clientX","clientY"];y.prototype={handler:function(){},init:function(){this.evEl&&n(this.element,this.evEl,this.domHandler),this.evTarget&&n(this.target,this.evTarget,this.domHandler),this.evWin&&n(x(this.element),this.evWin,this.domHandler)},destroy:function(){this.evEl&&o(this.element,this.evEl,this.domHandler),this.evTarget&&o(this.target,this.evTarget,this.domHandler),this.evWin&&o(x(this.element),this.evWin,this.domHandler)}};var Ma={mousedown:ya,mousemove:za,mouseup:Aa},Na="mousedown",Oa="mousemove mouseup";j(M,y,{handler:function(a){var b=Ma[a.type];b&ya&&0===a.button&&(this.pressed=!0),b&za&&1!==a.which&&(b=Aa),this.pressed&&this.allow&&(b&Aa&&(this.pressed=!1),this.callback(this.manager,b,{pointers:[a],changedPointers:[a],pointerType:va,srcEvent:a}))}});var Pa={pointerdown:ya,pointermove:za,pointerup:Aa,pointercancel:Ba,pointerout:Ba},Qa={2:ta,3:ua,4:va,5:wa},Ra="pointerdown",Sa="pointermove pointerup pointercancel";a.MSPointerEvent&&(Ra="MSPointerDown",Sa="MSPointerMove MSPointerUp MSPointerCancel"),j(N,y,{handler:function(a){var b=this.store,c=!1,d=a.type.toLowerCase().replace("ms",""),e=Pa[d],f=Qa[a.pointerType]||a.pointerType,g=f==ta,h=s(b,a.pointerId,"pointerId");e&ya&&(0===a.button||g)?0>h&&(b.push(a),h=b.length-1):e&(Aa|Ba)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Ta={touchstart:ya,touchmove:za,touchend:Aa,touchcancel:Ba},Ua="touchstart",Va="touchstart touchmove touchend touchcancel";j(O,y,{handler:function(a){var b=Ta[a.type];if(b===ya&&(this.started=!0),this.started){var c=P.call(this,a,b);b&(Aa|Ba)&&0===c[0].length-c[1].length&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:ta,srcEvent:a})}}});var Wa={touchstart:ya,touchmove:za,touchend:Aa,touchcancel:Ba},Xa="touchstart touchmove touchend touchcancel";j(Q,y,{handler:function(a){var b=Wa[a.type],c=R.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:ta,srcEvent:a})}}),j(S,y,{handler:function(a,b,c){var d=c.pointerType==ta,e=c.pointerType==va;if(d)this.mouse.allow=!1;else if(e&&!this.mouse.allow)return;b&(Aa|Ba)&&(this.mouse.allow=!0),this.callback(a,b,c)},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var Ya=v(ja.style,"touchAction"),Za=Ya!==d,$a="compute",_a="auto",ab="manipulation",bb="none",cb="pan-x",db="pan-y";T.prototype={set:function(a){a==$a&&(a=this.compute()),Za&&(this.manager.element.style[Ya]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){l(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),U(a.join(" "))},preventDefaults:function(a){if(!Za){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=q(d,bb),f=q(d,db),g=q(d,cb);return e||f&&c&Ha||g&&c&Ia?this.preventSrc(b):void 0}},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var eb=1,fb=2,gb=4,hb=8,ib=hb,jb=16,kb=32;V.prototype={defaults:{},set:function(a){return h(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=Y(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=Y(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=Y(a,this),-1===s(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=Y(a,this);var b=s(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(c.options.event+(b?W(d):""),a)}var c=this,d=this.state;hb>d&&b(!0),b(),d>=hb&&b(!0)},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=kb)},canEmit:function(){for(var a=0;a<this.requireFail.length;){if(!(this.requireFail[a].state&(kb|eb)))return!1;a++}return!0},recognize:function(a){var b=h({},a);return l(this.options.enable,[this,b])?(this.state&(ib|jb|kb)&&(this.state=eb),this.state=this.process(b),void(this.state&(fb|gb|hb|jb)&&this.tryEmit(b))):(this.reset(),void(this.state=kb))},process:function(){},getTouchAction:function(){},reset:function(){}},j(Z,V,{defaults:{pointers:1},attrTest:function(a){var b=this.options.pointers;return 0===b||a.pointers.length===b},process:function(a){var b=this.state,c=a.eventType,d=b&(fb|gb),e=this.attrTest(a);return d&&(c&Ba||!e)?b|jb:d||e?c&Aa?b|hb:b&fb?b|gb:fb:kb}}),j($,Z,{defaults:{event:"pan",threshold:10,pointers:1,direction:Ja},getTouchAction:function(){var a=this.options.direction,b=[];return a&Ha&&b.push(db),a&Ia&&b.push(cb),b},directionTest:function(a){var b=this.options,c=!0,d=a.distance,e=a.direction,f=a.deltaX,g=a.deltaY;return e&b.direction||(b.direction&Ha?(e=0===f?Ca:0>f?Da:Ea,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ca:0>g?Fa:Ga,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return Z.prototype.attrTest.call(this,a)&&(this.state&fb||!(this.state&fb)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=X(a.direction);b&&this.manager.emit(this.options.event+b,a),this._super.emit.call(this,a)}}),j(_,Z,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[bb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&fb)},emit:function(a){if(this._super.emit.call(this,a),1!==a.scale){var b=a.scale<1?"in":"out";this.manager.emit(this.options.event+b,a)}}}),j(aa,V,{defaults:{event:"press",pointers:1,time:500,threshold:5},getTouchAction:function(){return[_a]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance<b.threshold,f=a.deltaTime>b.time;if(this._input=a,!d||!c||a.eventType&(Aa|Ba)&&!f)this.reset();else if(a.eventType&ya)this.reset(),this._timer=e(function(){this.state=ib,this.tryEmit()},b.time,this);else if(a.eventType&Aa)return ib;return kb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===ib&&(a&&a.eventType&Aa?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=na(),this.manager.emit(this.options.event,this._input)))}}),j(ba,Z,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[bb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&fb)}}),j(ca,Z,{defaults:{event:"swipe",threshold:10,velocity:.65,direction:Ha|Ia,pointers:1},getTouchAction:function(){return $.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Ha|Ia)?b=a.velocity:c&Ha?b=a.velocityX:c&Ia&&(b=a.velocityY),this._super.attrTest.call(this,a)&&c&a.direction&&a.distance>this.options.threshold&&ma(b)>this.options.velocity&&a.eventType&Aa},emit:function(a){var b=X(a.direction);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),j(da,V,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:2,posThreshold:10},getTouchAction:function(){return[ab]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance<b.threshold,f=a.deltaTime<b.time;if(this.reset(),a.eventType&ya&&0===this.count)return this.failTimeout();if(d&&f&&c){if(a.eventType!=Aa)return this.failTimeout();var g=!this.pTime||a.timeStamp-this.pTime<b.interval,h=!this.pCenter||I(this.pCenter,a.center)<b.posThreshold;this.pTime=a.timeStamp,this.pCenter=a.center,h&&g?this.count+=1:this.count=1,this._input=a;var i=this.count%b.taps;if(0===i)return this.hasRequireFailures()?(this._timer=e(function(){this.state=ib,this.tryEmit()},b.interval,this),fb):ib}return kb},failTimeout:function(){return this._timer=e(function(){this.state=kb},this.options.interval,this),kb},reset:function(){clearTimeout(this._timer)},emit:function(){this.state==ib&&(this._input.tapCount=this.count,this.manager.emit(this.options.event,this._input))}}),ea.VERSION="2.0.4",ea.defaults={domEvents:!1,touchAction:$a,enable:!0,inputTarget:null,inputClass:null,preset:[[ba,{enable:!1}],[_,{enable:!1},["rotate"]],[ca,{direction:Ha}],[$,{direction:Ha},["swipe"]],[da],[da,{event:"doubletap",taps:2},["tap"]],[aa]],cssProps:{userSelect:"default",touchSelect:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}};var lb=1,mb=2;fa.prototype={set:function(a){return h(this.options,a),a.touchAction&&this.touchAction.update(),a.inputTarget&&(this.input.destroy(),this.input.target=a.inputTarget,this.input.init()),this},stop:function(a){this.session.stopped=a?mb:lb},recognize:function(a){var b=this.session;if(!b.stopped){this.touchAction.preventDefaults(a);var c,d=this.recognizers,e=b.curRecognizer;(!e||e&&e.state&ib)&&(e=b.curRecognizer=null);for(var f=0;f<d.length;)c=d[f],b.stopped===mb||e&&c!=e&&!c.canRecognizeWith(e)?c.reset():c.recognize(a),!e&&c.state&(fb|gb|hb)&&(e=b.curRecognizer=c),f++}},get:function(a){if(a instanceof V)return a;for(var b=this.recognizers,c=0;c<b.length;c++)if(b[c].options.event==a)return b[c];return null},add:function(a){if(f(a,"add",this))return this;var b=this.get(a.options.event);return b&&this.remove(b),this.recognizers.push(a),a.manager=this,this.touchAction.update(),a},remove:function(a){if(f(a,"remove",this))return this;var b=this.recognizers;return a=this.get(a),b.splice(s(b,a),1),this.touchAction.update(),this},on:function(a,b){var c=this.handlers;return g(r(a),function(a){c[a]=c[a]||[],c[a].push(b)}),this},off:function(a,b){var c=this.handlers;return g(r(a),function(a){b?c[a].splice(s(c[a],b),1):delete c[a]}),this},emit:function(a,b){this.options.domEvents&&ha(a,b);var c=this.handlers[a]&&this.handlers[a].slice();if(c&&c.length){b.type=a,b.preventDefault=function(){b.srcEvent.preventDefault()};for(var d=0;d<c.length;)c[d](b),d++}},destroy:function(){this.element&&ga(this,!1),this.handlers={},this.session={},this.input.destroy(),this.element=null}},h(ea,{INPUT_START:ya,INPUT_MOVE:za,INPUT_END:Aa,INPUT_CANCEL:Ba,STATE_POSSIBLE:eb,STATE_BEGAN:fb,STATE_CHANGED:gb,STATE_ENDED:hb,STATE_RECOGNIZED:ib,STATE_CANCELLED:jb,STATE_FAILED:kb,DIRECTION_NONE:Ca,DIRECTION_LEFT:Da,DIRECTION_RIGHT:Ea,DIRECTION_UP:Fa,DIRECTION_DOWN:Ga,DIRECTION_HORIZONTAL:Ha,DIRECTION_VERTICAL:Ia,DIRECTION_ALL:Ja,Manager:fa,Input:y,TouchAction:T,TouchInput:Q,MouseInput:M,PointerEventInput:N,TouchMouseInput:S,SingleTouchInput:O,Recognizer:V,AttrRecognizer:Z,Tap:da,Pan:$,Swipe:ca,Pinch:_,Rotate:ba,Press:aa,on:n,off:o,each:g,merge:i,extend:h,inherit:j,bindFn:k,prefixed:v}),typeof define==ka&&define.amd?define(function(){return ea}):"undefined"!=typeof module&&module.exports?module.exports=ea:a[c]=ea}(window,document,"Hammer"),function(a){"function"==typeof define&&define.amd?define(["jquery","hammerjs"],a):"object"==typeof exports?a(require("jquery"),require("hammerjs")):a(jQuery,Hammer)}(function(a,b){function c(c,d){var e=a(c);e.data("hammer")||e.data("hammer",new b(e[0],d))}a.fn.hammer=function(a){return this.each(function(){c(this,a)})},b.Manager.prototype.emit=function(b){return function(c,d){b.call(this,c,d),a(this.element).trigger({type:c,gesture:d})}}(b.Manager.prototype.emit)}),function(a){a.Package?Materialize={}:a.Materialize={}}(window),function(a){for(var b=0,c=["webkit","moz"],d=a.requestAnimationFrame,e=a.cancelAnimationFrame,f=c.length;--f>=0&&!d;)d=a[c[f]+"RequestAnimationFrame"],e=a[c[f]+"CancelRequestAnimationFrame"];d&&e||(d=function(a){var c=+Date.now(),d=Math.max(b+16,c);return setTimeout(function(){a(b=d)},d-c)},e=clearTimeout),a.requestAnimationFrame=d,a.cancelAnimationFrame=e}(window),Materialize.guid=function(){function a(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}}(),Materialize.escapeHash=function(a){return a.replace(/(:|\.|\[|\]|,|=)/g,"\\$1")},Materialize.elementOrParentIsFixed=function(a){var b=$(a),c=b.add(b.parents()),d=!1;return c.each(function(){if("fixed"===$(this).css("position"))return d=!0,!1}),d};var getTime=Date.now||function(){return(new Date).getTime()};Materialize.throttle=function(a,b,c){var d,e,f,g=null,h=0;c||(c={});var i=function(){h=c.leading===!1?0:getTime(),g=null,f=a.apply(d,e),d=e=null};return function(){var j=getTime();h||c.leading!==!1||(h=j);var k=b-(j-h);return d=this,e=arguments,k<=0?(clearTimeout(g),g=null,h=j,f=a.apply(d,e),d=e=null):g||c.trailing===!1||(g=setTimeout(i,k)),f}};var Vel;Vel=jQuery?jQuery.Velocity:$?$.Velocity:Velocity,function(a){a.fn.collapsible=function(b){var c={accordion:void 0,onOpen:void 0,onClose:void 0};return b=a.extend(c,b),this.each(function(){function c(b){j=i.find("> li > .collapsible-header"),b.hasClass("active")?b.parent().addClass("active"):b.parent().removeClass("active"),b.parent().hasClass("active")?b.siblings(".collapsible-body").stop(!0,!1).slideDown({duration:350,easing:"easeOutQuart",queue:!1,complete:function(){a(this).css("height","")}}):b.siblings(".collapsible-body").stop(!0,!1).slideUp({duration:350,easing:"easeOutQuart",queue:!1,complete:function(){a(this).css("height","")}}),j.not(b).removeClass("active").parent().removeClass("active"),j.not(b).parent().children(".collapsible-body").stop(!0,!1).each(function(){a(this).is(":visible")&&a(this).slideUp({duration:350,easing:"easeOutQuart",queue:!1,complete:function(){a(this).css("height",""),f(a(this).siblings(".collapsible-header"))}})})}function d(b){b.hasClass("active")?b.parent().addClass("active"):b.parent().removeClass("active"),b.parent().hasClass("active")?b.siblings(".collapsible-body").stop(!0,!1).slideDown({duration:350,easing:"easeOutQuart",queue:!1,complete:function(){a(this).css("height","")}}):b.siblings(".collapsible-body").stop(!0,!1).slideUp({duration:350,easing:"easeOutQuart",queue:!1,complete:function(){a(this).css("height","")}})}function e(a){b.accordion||"accordion"===k||void 0===k?c(a):d(a),f(a)}function f(a){a.hasClass("active")?"function"==typeof b.onOpen&&b.onOpen.call(this,a.parent()):"function"==typeof b.onClose&&b.onClose.call(this,a.parent())}function g(a){var b=h(a);return b.length>0}function h(a){return a.closest("li > .collapsible-header")}var i=a(this),j=a(this).find("> li > .collapsible-header"),k=i.data("collapsible");i.off("click.collapse","> li > .collapsible-header"),j.off("click.collapse"),i.on("click.collapse","> li > .collapsible-header",function(b){var c=a(b.target);g(c)&&(c=h(c)),c.toggleClass("active"),e(c)}),b.accordion||"accordion"===k||void 0===k?e(j.filter(".active").first()):j.filter(".active").each(function(){e(a(this))})})},a(document).ready(function(){a(".collapsible").collapsible()})}(jQuery),function(a){a.fn.scrollTo=function(b){return a(this).scrollTop(a(this).scrollTop()-a(this).offset().top+a(b).offset().top),this},a.fn.dropdown=function(b){var c={inDuration:300,outDuration:225,constrainWidth:!0,hover:!1,gutter:0,belowOrigin:!1,alignment:"left",stopPropagation:!1};return"open"===b?(this.each(function(){a(this).trigger("open")}),!1):"close"===b?(this.each(function(){a(this).trigger("close")}),!1):void this.each(function(){function d(){void 0!==g.data("induration")&&(h.inDuration=g.data("induration")),void 0!==g.data("outduration")&&(h.outDuration=g.data("outduration")),void 0!==g.data("constrainwidth")&&(h.constrainWidth=g.data("constrainwidth")),void 0!==g.data("hover")&&(h.hover=g.data("hover")),void 0!==g.data("gutter")&&(h.gutter=g.data("gutter")),void 0!==g.data("beloworigin")&&(h.belowOrigin=g.data("beloworigin")),void 0!==g.data("alignment")&&(h.alignment=g.data("alignment")),void 0!==g.data("stoppropagation")&&(h.stopPropagation=g.data("stoppropagation"))}function e(b){"focus"===b&&(i=!0),d(),j.addClass("active"),g.addClass("active"),h.constrainWidth===!0?j.css("width",g.outerWidth()):j.css("white-space","nowrap");var c=window.innerHeight,e=g.innerHeight(),k=g.offset().left,l=g.offset().top-a(window).scrollTop(),m=h.alignment,n=0,o=0,p=0;h.belowOrigin===!0&&(p=e);var q=0,r=0,s=g.parent();if(s.is("body")||(s[0].scrollHeight>s[0].clientHeight&&(q=s[0].scrollTop),s[0].scrollWidth>s[0].clientWidth&&(r=s[0].scrollLeft)),k+j.innerWidth()>a(window).width()?m="right":k-j.innerWidth()+g.innerWidth()<0&&(m="left"),l+j.innerHeight()>c)if(l+e-j.innerHeight()<0){var t=c-l-p;j.css("max-height",t)}else p||(p+=e),p-=j.innerHeight();if("left"===m)n=h.gutter,o=g.position().left+n;else if("right"===m){var u=g.position().left+g.outerWidth()-j.outerWidth();n=-h.gutter,o=u+n}j.css({position:"absolute",top:g.position().top+p+q,left:o+r}),j.stop(!0,!0).css("opacity",0).slideDown({queue:!1,duration:h.inDuration,easing:"easeOutCubic",complete:function(){a(this).css("height","")}}).animate({opacity:1},{queue:!1,duration:h.inDuration,easing:"easeOutSine"}),a(document).bind("click."+j.attr("id")+" touchstart."+j.attr("id"),function(b){j.is(b.target)||g.is(b.target)||g.find(b.target).length||(f(),a(document).unbind("click."+j.attr("id")+" touchstart."+j.attr("id")))})}function f(){i=!1,j.fadeOut(h.outDuration),j.removeClass("active"),g.removeClass("active"),a(document).unbind("click."+j.attr("id")+" touchstart."+j.attr("id")),setTimeout(function(){j.css("max-height","")},h.outDuration)}var g=a(this),h=a.extend({},c,b),i=!1,j=a("#"+g.attr("data-activates"));if(d(),g.after(j),h.hover){var k=!1;g.unbind("click."+g.attr("id")),g.on("mouseenter",function(a){k===!1&&(e(),k=!0)}),g.on("mouseleave",function(b){var c=b.toElement||b.relatedTarget;a(c).closest(".dropdown-content").is(j)||(j.stop(!0,!0),f(),k=!1)}),j.on("mouseleave",function(b){var c=b.toElement||b.relatedTarget;a(c).closest(".dropdown-button").is(g)||(j.stop(!0,!0),f(),k=!1)})}else g.unbind("click."+g.attr("id")),g.bind("click."+g.attr("id"),function(b){i||(g[0]!=b.currentTarget||g.hasClass("active")||0!==a(b.target).closest(".dropdown-content").length?g.hasClass("active")&&(f(),a(document).unbind("click."+j.attr("id")+" touchstart."+j.attr("id"))):(b.preventDefault(),h.stopPropagation&&b.stopPropagation(),e("click")))});g.on("open",function(a,b){e(b)}),g.on("close",f)})},a(document).ready(function(){a(".dropdown-button").dropdown()})}(jQuery),function(a){var b=0,c=0,d=function(){return c++,"materialize-modal-overlay-"+c},e={init:function(c){var e={opacity:.5,inDuration:350,outDuration:250,ready:void 0,
+complete:void 0,dismissible:!0,startingTop:"4%",endingTop:"10%"};return c=a.extend(e,c),this.each(function(){var e=a(this),f=a(this).attr("id")||"#"+a(this).data("target"),g=function(){var d=e.data("overlay-id"),f=a("#"+d);e.removeClass("open"),a("body").css({overflow:"",width:""}),e.find(".modal-close").off("click.close"),a(document).off("keyup.modal"+d),f.velocity({opacity:0},{duration:c.outDuration,queue:!1,ease:"easeOutQuart"});var g={duration:c.outDuration,queue:!1,ease:"easeOutCubic",complete:function(){a(this).css({display:"none"}),"function"==typeof c.complete&&c.complete.call(this,e),f.remove(),b--}};e.hasClass("bottom-sheet")?e.velocity({bottom:"-100%",opacity:0},g):e.velocity({top:c.startingTop,opacity:0,scaleX:.7},g)},h=function(f){var h=a("body"),i=h.innerWidth();if(h.css("overflow","hidden"),h.width(i),!e.hasClass("open")){var j=d(),k=a('<div class="modal-overlay"></div>');lStack=++b,k.attr("id",j).css("z-index",1e3+2*lStack),e.data("overlay-id",j).css("z-index",1e3+2*lStack+1),e.addClass("open"),a("body").append(k),c.dismissible&&(k.click(function(){g()}),a(document).on("keyup.modal"+j,function(a){27===a.keyCode&&g()})),e.find(".modal-close").on("click.close",function(a){g()}),k.css({display:"block",opacity:0}),e.css({display:"block",opacity:0}),k.velocity({opacity:c.opacity},{duration:c.inDuration,queue:!1,ease:"easeOutCubic"}),e.data("associated-overlay",k[0]);var l={duration:c.inDuration,queue:!1,ease:"easeOutCubic",complete:function(){"function"==typeof c.ready&&c.ready.call(this,e,f)}};e.hasClass("bottom-sheet")?e.velocity({bottom:"0",opacity:1},l):(a.Velocity.hook(e,"scaleX",.7),e.css({top:c.startingTop}),e.velocity({top:c.endingTop,opacity:1,scaleX:"1"},l))}};a(document).off("click.modalTrigger",'a[href="#'+f+'"], [data-target="'+f+'"]'),a(this).off("openModal"),a(this).off("closeModal"),a(document).on("click.modalTrigger",'a[href="#'+f+'"], [data-target="'+f+'"]',function(b){c.startingTop=(a(this).offset().top-a(window).scrollTop())/1.15,h(a(this)),b.preventDefault()}),a(this).on("openModal",function(){a(this).attr("href")||"#"+a(this).data("target");h()}),a(this).on("closeModal",function(){g()})})},open:function(){a(this).trigger("openModal")},close:function(){a(this).trigger("closeModal")}};a.fn.modal=function(b){return e[b]?e[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?void a.error("Method "+b+" does not exist on jQuery.modal"):e.init.apply(this,arguments)}}(jQuery),function(a){a.fn.materialbox=function(){return this.each(function(){function b(){f=!1;var b=i.parent(".material-placeholder"),d=(window.innerWidth,window.innerHeight,i.data("width")),g=i.data("height");i.velocity("stop",!0),a("#materialbox-overlay").velocity("stop",!0),a(".materialbox-caption").velocity("stop",!0),a("#materialbox-overlay").velocity({opacity:0},{duration:h,queue:!1,easing:"easeOutQuad",complete:function(){e=!1,a(this).remove()}}),i.velocity({width:d,height:g,left:0,top:0},{duration:h,queue:!1,easing:"easeOutQuad"}),a(".materialbox-caption").velocity({opacity:0},{duration:h,queue:!1,easing:"easeOutQuad",complete:function(){b.css({height:"",width:"",position:"",top:"",left:""}),i.css({height:"",top:"",left:"",width:"","max-width":"",position:"","z-index":"","will-change":""}),i.removeClass("active"),f=!0,a(this).remove(),c&&c.css("overflow","")}})}if(!a(this).hasClass("initialized")){a(this).addClass("initialized");var c,d,e=!1,f=!0,g=275,h=200,i=a(this),j=a("<div></div>").addClass("material-placeholder");i.wrap(j),i.on("click",function(){var h=i.parent(".material-placeholder"),j=window.innerWidth,k=window.innerHeight,l=i.width(),m=i.height();if(f===!1)return b(),!1;if(e&&f===!0)return b(),!1;f=!1,i.addClass("active"),e=!0,h.css({width:h[0].getBoundingClientRect().width,height:h[0].getBoundingClientRect().height,position:"relative",top:0,left:0}),c=void 0,d=h[0].parentNode;for(;null!==d&&!a(d).is(document);){var n=a(d);"visible"!==n.css("overflow")&&(n.css("overflow","visible"),c=void 0===c?n:c.add(n)),d=d.parentNode}i.css({position:"absolute","z-index":1e3,"will-change":"left, top, width, height"}).data("width",l).data("height",m);var o=a('<div id="materialbox-overlay"></div>').css({opacity:0}).click(function(){f===!0&&b()});i.before(o);var p=o[0].getBoundingClientRect();if(o.css({width:j,height:k,left:-1*p.left,top:-1*p.top}),o.velocity({opacity:1},{duration:g,queue:!1,easing:"easeOutQuad"}),""!==i.data("caption")){var q=a('<div class="materialbox-caption"></div>');q.text(i.data("caption")),a("body").append(q),q.css({display:"inline"}),q.velocity({opacity:1},{duration:g,queue:!1,easing:"easeOutQuad"})}var r=0,s=l/j,t=m/k,u=0,v=0;s>t?(r=m/l,u=.9*j,v=.9*j*r):(r=l/m,u=.9*k*r,v=.9*k),i.hasClass("responsive-img")?i.velocity({"max-width":u,width:l},{duration:0,queue:!1,complete:function(){i.css({left:0,top:0}).velocity({height:v,width:u,left:a(document).scrollLeft()+j/2-i.parent(".material-placeholder").offset().left-u/2,top:a(document).scrollTop()+k/2-i.parent(".material-placeholder").offset().top-v/2},{duration:g,queue:!1,easing:"easeOutQuad",complete:function(){f=!0}})}}):i.css("left",0).css("top",0).velocity({height:v,width:u,left:a(document).scrollLeft()+j/2-i.parent(".material-placeholder").offset().left-u/2,top:a(document).scrollTop()+k/2-i.parent(".material-placeholder").offset().top-v/2},{duration:g,queue:!1,easing:"easeOutQuad",complete:function(){f=!0}})}),a(window).scroll(function(){e&&b()}),a(document).keyup(function(a){27===a.keyCode&&f===!0&&e&&b()})}})},a(document).ready(function(){a(".materialboxed").materialbox()})}(jQuery),function(a){a.fn.parallax=function(){var b=a(window).width();return this.each(function(c){function d(c){var d;d=b<601?e.height()>0?e.height():e.children("img").height():e.height()>0?e.height():500;var f=e.children("img").first(),g=f.height(),h=g-d,i=e.offset().top+d,j=e.offset().top,k=a(window).scrollTop(),l=window.innerHeight,m=k+l,n=(m-j)/(d+l),o=Math.round(h*n);c&&f.css("display","block"),i>k&&j<k+l&&f.css("transform","translate3D(-50%,"+o+"px, 0)")}var e=a(this);e.addClass("parallax"),e.children("img").one("load",function(){d(!0)}).each(function(){this.complete&&a(this).trigger("load")}),a(window).scroll(function(){b=a(window).width(),d(!1)}),a(window).resize(function(){b=a(window).width(),d(!1)})})}}(jQuery),function(a){var b={init:function(b){var c={onShow:null,swipeable:!1,responsiveThreshold:1/0};return b=a.extend(c,b),this.each(function(){var c,d,e,f,g,h=a(this),i=a(window).width(),j=h.find("li.tab a"),k=h.width(),l=a(),m=Math.max(k,h[0].scrollWidth)/j.length,n=prev_index=0,o=!1,p=300,q=function(a){return k-a.position().left-a.outerWidth()-h.scrollLeft()},r=function(a){return a.position().left+h.scrollLeft()},s=function(a){n-a>=0?(f.velocity({right:q(c)},{duration:p,queue:!1,easing:"easeOutQuad"}),f.velocity({left:r(c)},{duration:p,queue:!1,easing:"easeOutQuad",delay:90})):(f.velocity({left:r(c)},{duration:p,queue:!1,easing:"easeOutQuad"}),f.velocity({right:q(c)},{duration:p,queue:!1,easing:"easeOutQuad",delay:90}))};b.swipeable&&i>b.responsiveThreshold&&(b.swipeable=!1),c=a(j.filter('[href="'+location.hash+'"]')),0===c.length&&(c=a(this).find("li.tab a.active").first()),0===c.length&&(c=a(this).find("li.tab a").first()),c.addClass("active"),n=j.index(c),n<0&&(n=0),void 0!==c[0]&&(d=a(c[0].hash),d.addClass("active")),h.find(".indicator").length||h.append('<div class="indicator"></div>'),f=h.find(".indicator"),h.append(f),h.is(":visible")&&setTimeout(function(){f.css({right:q(c)}),f.css({left:r(c)})},0),a(window).resize(function(){k=h.width(),m=Math.max(k,h[0].scrollWidth)/j.length,n<0&&(n=0),0!==m&&0!==k&&(f.css({right:q(c)}),f.css({left:r(c)}))}),b.swipeable?(j.each(function(){var b=a(Materialize.escapeHash(this.hash));b.addClass("carousel-item"),l=l.add(b)}),e=l.wrapAll('<div class="tabs-content carousel"></div>'),l.css("display",""),a(".tabs-content.carousel").carousel({fullWidth:!0,noWrap:!0,onCycleTo:function(a){if(!o){var b=n;n=e.index(a),c=j.eq(n),s(b)}}})):j.not(c).each(function(){a(Materialize.escapeHash(this.hash)).hide()}),h.on("click","a",function(e){if(a(this).parent().hasClass("disabled"))return void e.preventDefault();if(!a(this).attr("target")){o=!0,k=h.width(),m=Math.max(k,h[0].scrollWidth)/j.length,c.removeClass("active");var f=d;c=a(this),d=a(Materialize.escapeHash(this.hash)),j=h.find("li.tab a");c.position();c.addClass("active"),prev_index=n,n=j.index(a(this)),n<0&&(n=0),b.swipeable?l.length&&l.carousel("set",n):(void 0!==d&&(d.show(),d.addClass("active"),"function"==typeof b.onShow&&b.onShow.call(this,d)),void 0===f||f.is(d)||(f.hide(),f.removeClass("active"))),g=setTimeout(function(){o=!1},p),s(prev_index),e.preventDefault()}})})},select_tab:function(a){this.find('a[href="#'+a+'"]').trigger("click")}};a.fn.tabs=function(c){return b[c]?b[c].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof c&&c?void a.error("Method "+c+" does not exist on jQuery.tabs"):b.init.apply(this,arguments)},a(document).ready(function(){a("ul.tabs").tabs()})}(jQuery),function(a){a.fn.tooltip=function(c){var d=5,e={delay:350,tooltip:"",position:"bottom",html:!1};return"remove"===c?(this.each(function(){a("#"+a(this).attr("data-tooltip-id")).remove(),a(this).off("mouseenter.tooltip mouseleave.tooltip")}),!1):(c=a.extend(e,c),this.each(function(){var e=Materialize.guid(),f=a(this);f.attr("data-tooltip-id")&&a("#"+f.attr("data-tooltip-id")).remove(),f.attr("data-tooltip-id",e);var g,h,i,j,k,l,m=function(){g=f.attr("data-html")?"true"===f.attr("data-html"):c.html,h=f.attr("data-delay"),h=void 0===h||""===h?c.delay:h,i=f.attr("data-position"),i=void 0===i||""===i?c.position:i,j=f.attr("data-tooltip"),j=void 0===j||""===j?c.tooltip:j};m();var n=function(){var b=a('<div class="material-tooltip"></div>');return j=g?a("<span></span>").html(j):a("<span></span>").text(j),b.append(j).appendTo(a("body")).attr("id",e),l=a('<div class="backdrop"></div>'),l.appendTo(b),b};k=n(),f.off("mouseenter.tooltip mouseleave.tooltip");var o,p=!1;f.on({"mouseenter.tooltip":function(a){var c=function(){m(),p=!0,k.velocity("stop"),l.velocity("stop"),k.css({visibility:"visible",left:"0px",top:"0px"});var a,c,e,g=f.outerWidth(),h=f.outerHeight(),j=k.outerHeight(),n=k.outerWidth(),o="0px",q="0px",r=l[0].offsetWidth,s=l[0].offsetHeight,t=8,u=8,v=0;"top"===i?(a=f.offset().top-j-d,c=f.offset().left+g/2-n/2,e=b(c,a,n,j),o="-10px",l.css({bottom:0,left:0,borderRadius:"14px 14px 0 0",transformOrigin:"50% 100%",marginTop:j,marginLeft:n/2-r/2})):"left"===i?(a=f.offset().top+h/2-j/2,c=f.offset().left-n-d,e=b(c,a,n,j),q="-10px",l.css({top:"-7px",right:0,width:"14px",height:"14px",borderRadius:"14px 0 0 14px",transformOrigin:"95% 50%",marginTop:j/2,marginLeft:n})):"right"===i?(a=f.offset().top+h/2-j/2,c=f.offset().left+g+d,e=b(c,a,n,j),q="+10px",l.css({top:"-7px",left:0,width:"14px",height:"14px",borderRadius:"0 14px 14px 0",transformOrigin:"5% 50%",marginTop:j/2,marginLeft:"0px"})):(a=f.offset().top+f.outerHeight()+d,c=f.offset().left+g/2-n/2,e=b(c,a,n,j),o="+10px",l.css({top:0,left:0,marginLeft:n/2-r/2})),k.css({top:e.y,left:e.x}),t=Math.SQRT2*n/parseInt(r),u=Math.SQRT2*j/parseInt(s),v=Math.max(t,u),k.velocity({translateY:o,translateX:q},{duration:350,queue:!1}).velocity({opacity:1},{duration:300,delay:50,queue:!1}),l.css({visibility:"visible"}).velocity({opacity:1},{duration:55,delay:0,queue:!1}).velocity({scaleX:v,scaleY:v},{duration:300,delay:0,queue:!1,easing:"easeInOutQuad"})};o=setTimeout(c,h)},"mouseleave.tooltip":function(){p=!1,clearTimeout(o),setTimeout(function(){p!==!0&&(k.velocity({opacity:0,translateY:0,translateX:0},{duration:225,queue:!1}),l.velocity({opacity:0,scaleX:1,scaleY:1},{duration:225,queue:!1,complete:function(){l.css({visibility:"hidden"}),k.css({visibility:"hidden"}),p=!1}}))},225)}})}))};var b=function(b,c,d,e){var f=b,g=c;return f<0?f=4:f+d>window.innerWidth&&(f-=f+d-window.innerWidth),g<0?g=4:g+e>window.innerHeight+a(window).scrollTop&&(g-=g+e-window.innerHeight),{x:f,y:g}};a(document).ready(function(){a(".tooltipped").tooltip()})}(jQuery),function(a){"use strict";function b(a){return null!==a&&a===a.window}function c(a){return b(a)?a:9===a.nodeType&&a.defaultView}function d(a){var b,d,e={top:0,left:0},f=a&&a.ownerDocument;return b=f.documentElement,"undefined"!=typeof a.getBoundingClientRect&&(e=a.getBoundingClientRect()),d=c(f),{top:e.top+d.pageYOffset-b.clientTop,left:e.left+d.pageXOffset-b.clientLeft}}function e(a){var b="";for(var c in a)a.hasOwnProperty(c)&&(b+=c+":"+a[c]+";");return b}function f(a){if(k.allowEvent(a)===!1)return null;for(var b=null,c=a.target||a.srcElement;null!==c.parentElement;){if(!(c instanceof SVGElement||c.className.indexOf("waves-effect")===-1)){b=c;break}if(c.classList.contains("waves-effect")){b=c;break}c=c.parentElement}return b}function g(b){var c=f(b);null!==c&&(j.show(b,c),"ontouchstart"in a&&(c.addEventListener("touchend",j.hide,!1),c.addEventListener("touchcancel",j.hide,!1)),c.addEventListener("mouseup",j.hide,!1),c.addEventListener("mouseleave",j.hide,!1))}var h=h||{},i=document.querySelectorAll.bind(document),j={duration:750,show:function(a,b){if(2===a.button)return!1;var c=b||this,f=document.createElement("div");f.className="waves-ripple",c.appendChild(f);var g=d(c),h=a.pageY-g.top,i=a.pageX-g.left,k="scale("+c.clientWidth/100*10+")";"touches"in a&&(h=a.touches[0].pageY-g.top,i=a.touches[0].pageX-g.left),f.setAttribute("data-hold",Date.now()),f.setAttribute("data-scale",k),f.setAttribute("data-x",i),f.setAttribute("data-y",h);var l={top:h+"px",left:i+"px"};f.className=f.className+" waves-notransition",f.setAttribute("style",e(l)),f.className=f.className.replace("waves-notransition",""),l["-webkit-transform"]=k,l["-moz-transform"]=k,l["-ms-transform"]=k,l["-o-transform"]=k,l.transform=k,l.opacity="1",l["-webkit-transition-duration"]=j.duration+"ms",l["-moz-transition-duration"]=j.duration+"ms",l["-o-transition-duration"]=j.duration+"ms",l["transition-duration"]=j.duration+"ms",l["-webkit-transition-timing-function"]="cubic-bezier(0.250, 0.460, 0.450, 0.940)",l["-moz-transition-timing-function"]="cubic-bezier(0.250, 0.460, 0.450, 0.940)",l["-o-transition-timing-function"]="cubic-bezier(0.250, 0.460, 0.450, 0.940)",l["transition-timing-function"]="cubic-bezier(0.250, 0.460, 0.450, 0.940)",f.setAttribute("style",e(l))},hide:function(a){k.touchup(a);var b=this,c=(1.4*b.clientWidth,null),d=b.getElementsByClassName("waves-ripple");if(!(d.length>0))return!1;c=d[d.length-1];var f=c.getAttribute("data-x"),g=c.getAttribute("data-y"),h=c.getAttribute("data-scale"),i=Date.now()-Number(c.getAttribute("data-hold")),l=350-i;l<0&&(l=0),setTimeout(function(){var a={top:g+"px",left:f+"px",opacity:"0","-webkit-transition-duration":j.duration+"ms","-moz-transition-duration":j.duration+"ms","-o-transition-duration":j.duration+"ms","transition-duration":j.duration+"ms","-webkit-transform":h,"-moz-transform":h,"-ms-transform":h,"-o-transform":h,transform:h};c.setAttribute("style",e(a)),setTimeout(function(){try{b.removeChild(c)}catch(a){return!1}},j.duration)},l)},wrapInput:function(a){for(var b=0;b<a.length;b++){var c=a[b];if("input"===c.tagName.toLowerCase()){var d=c.parentNode;if("i"===d.tagName.toLowerCase()&&d.className.indexOf("waves-effect")!==-1)continue;var e=document.createElement("i");e.className=c.className+" waves-input-wrapper";var f=c.getAttribute("style");f||(f=""),e.setAttribute("style",f),c.className="waves-button-input",c.removeAttribute("style"),d.replaceChild(e,c),e.appendChild(c)}}}},k={touches:0,allowEvent:function(a){var b=!0;return"touchstart"===a.type?k.touches+=1:"touchend"===a.type||"touchcancel"===a.type?setTimeout(function(){k.touches>0&&(k.touches-=1)},500):"mousedown"===a.type&&k.touches>0&&(b=!1),b},touchup:function(a){k.allowEvent(a)}};h.displayEffect=function(b){b=b||{},"duration"in b&&(j.duration=b.duration),j.wrapInput(i(".waves-effect")),"ontouchstart"in a&&document.body.addEventListener("touchstart",g,!1),document.body.addEventListener("mousedown",g,!1)},h.attach=function(b){"input"===b.tagName.toLowerCase()&&(j.wrapInput([b]),b=b.parentElement),"ontouchstart"in a&&b.addEventListener("touchstart",g,!1),b.addEventListener("mousedown",g,!1)},a.Waves=h,document.addEventListener("DOMContentLoaded",function(){h.displayEffect()},!1)}(window),Materialize.toast=function(a,b,c,d){function e(a){var b=document.createElement("div");if(b.classList.add("toast"),c)for(var e=c.split(" "),f=0,g=e.length;f<g;f++)b.classList.add(e[f]);("object"==typeof HTMLElement?a instanceof HTMLElement:a&&"object"==typeof a&&null!==a&&1===a.nodeType&&"string"==typeof a.nodeName)?b.appendChild(a):a instanceof jQuery?b.appendChild(a[0]):b.innerHTML=a;var h=new Hammer(b,{prevent_default:!1});return h.on("pan",function(a){var c=a.deltaX,d=80;b.classList.contains("panning")||b.classList.add("panning");var e=1-Math.abs(c/d);e<0&&(e=0),Vel(b,{left:c,opacity:e},{duration:50,queue:!1,easing:"easeOutQuad"})}),h.on("panend",function(a){var c=a.deltaX,e=80;Math.abs(c)>e?Vel(b,{marginTop:"-40px"},{duration:375,easing:"easeOutExpo",queue:!1,complete:function(){"function"==typeof d&&d(),b.parentNode.removeChild(b)}}):(b.classList.remove("panning"),Vel(b,{left:0,opacity:1},{duration:300,easing:"easeOutExpo",queue:!1}))}),b}c=c||"";var f=document.getElementById("toast-container");null===f&&(f=document.createElement("div"),f.id="toast-container",document.body.appendChild(f));var g=e(a);a&&f.appendChild(g),g.style.opacity=0,Vel(g,{translateY:"-35px",opacity:1},{duration:300,easing:"easeOutCubic",queue:!1});var h,i=b;null!=i&&(h=setInterval(function(){null===g.parentNode&&window.clearInterval(h),g.classList.contains("panning")||(i-=20),i<=0&&(Vel(g,{opacity:0,marginTop:"-40px"},{duration:375,easing:"easeOutExpo",queue:!1,complete:function(){"function"==typeof d&&d(),this[0].parentNode.removeChild(this[0])}}),window.clearInterval(h))},20))},function(a){var b={init:function(b){var c={menuWidth:300,edge:"left",closeOnClick:!1,draggable:!0};b=a.extend(c,b),a(this).each(function(){var c=a(this),d=c.attr("data-activates"),e=a("#"+d);300!=b.menuWidth&&e.css("width",b.menuWidth);var f=a('.drag-target[data-sidenav="'+d+'"]');b.draggable?(f.length&&f.remove(),f=a('<div class="drag-target"></div>').attr("data-sidenav",d),a("body").append(f)):f=a(),"left"==b.edge?(e.css("transform","translateX(-100%)"),f.css({left:0})):(e.addClass("right-aligned").css("transform","translateX(100%)"),f.css({right:0})),e.hasClass("fixed")&&window.innerWidth>992&&e.css("transform","translateX(0)"),e.hasClass("fixed")&&a(window).resize(function(){window.innerWidth>992?0!==a("#sidenav-overlay").length&&i?g(!0):e.css("transform","translateX(0%)"):i===!1&&("left"===b.edge?e.css("transform","translateX(-100%)"):e.css("transform","translateX(100%)"))}),b.closeOnClick===!0&&e.on("click.itemclick","a:not(.collapsible-header)",function(){g()});var g=function(c){h=!1,i=!1,a("body").css({overflow:"",width:""}),a("#sidenav-overlay").velocity({opacity:0},{duration:200,queue:!1,easing:"easeOutQuad",complete:function(){a(this).remove()}}),"left"===b.edge?(f.css({width:"",right:"",left:"0"}),e.velocity({translateX:"-100%"},{duration:200,queue:!1,easing:"easeOutCubic",complete:function(){c===!0&&(e.removeAttr("style"),e.css("width",b.menuWidth))}})):(f.css({width:"",right:"0",left:""}),e.velocity({translateX:"100%"},{duration:200,queue:!1,easing:"easeOutCubic",complete:function(){c===!0&&(e.removeAttr("style"),e.css("width",b.menuWidth))}}))},h=!1,i=!1;b.draggable&&(f.on("click",function(){i&&g()}),f.hammer({prevent_default:!1}).bind("pan",function(c){if("touch"==c.gesture.pointerType){var d=(c.gesture.direction,c.gesture.center.x),f=(c.gesture.center.y,c.gesture.velocityX,a("body")),h=a("#sidenav-overlay"),j=f.innerWidth();if(f.css("overflow","hidden"),f.width(j),0===h.length&&(h=a('<div id="sidenav-overlay"></div>'),h.css("opacity",0).click(function(){g()}),a("body").append(h)),"left"===b.edge&&(d>b.menuWidth?d=b.menuWidth:d<0&&(d=0)),"left"===b.edge)d<b.menuWidth/2?i=!1:d>=b.menuWidth/2&&(i=!0),e.css("transform","translateX("+(d-b.menuWidth)+"px)");else{d<window.innerWidth-b.menuWidth/2?i=!0:d>=window.innerWidth-b.menuWidth/2&&(i=!1);var k=d-b.menuWidth/2;k<0&&(k=0),e.css("transform","translateX("+k+"px)")}var l;"left"===b.edge?(l=d/b.menuWidth,h.velocity({opacity:l},{duration:10,queue:!1,easing:"easeOutQuad"})):(l=Math.abs((d-window.innerWidth)/b.menuWidth),h.velocity({opacity:l},{duration:10,queue:!1,easing:"easeOutQuad"}))}}).bind("panend",function(c){if("touch"==c.gesture.pointerType){var d=a('<div id="sidenav-overlay"></div>'),g=c.gesture.velocityX,j=c.gesture.center.x,k=j-b.menuWidth,l=j-b.menuWidth/2;k>0&&(k=0),l<0&&(l=0),h=!1,"left"===b.edge?i&&g<=.3||g<-.5?(0!==k&&e.velocity({translateX:[0,k]},{duration:300,queue:!1,easing:"easeOutQuad"}),d.velocity({opacity:1},{duration:50,queue:!1,easing:"easeOutQuad"}),f.css({width:"50%",right:0,left:""}),i=!0):(!i||g>.3)&&(a("body").css({overflow:"",width:""}),e.velocity({translateX:[-1*b.menuWidth-10,k]},{duration:200,queue:!1,easing:"easeOutQuad"}),d.velocity({opacity:0},{duration:200,queue:!1,easing:"easeOutQuad",complete:function(){a(this).remove()}}),f.css({width:"10px",right:"",left:0})):i&&g>=-.3||g>.5?(0!==l&&e.velocity({translateX:[0,l]},{duration:300,queue:!1,easing:"easeOutQuad"}),d.velocity({opacity:1},{duration:50,queue:!1,easing:"easeOutQuad"}),f.css({width:"50%",right:"",left:0}),i=!0):(!i||g<-.3)&&(a("body").css({overflow:"",width:""}),e.velocity({translateX:[b.menuWidth+10,l]},{duration:200,queue:!1,easing:"easeOutQuad"}),d.velocity({opacity:0},{duration:200,queue:!1,easing:"easeOutQuad",complete:function(){a(this).remove()}}),f.css({width:"10px",right:0,left:""}))}})),c.off("click.sidenav").on("click.sidenav",function(){if(i===!0)i=!1,h=!1,g();else{var c=a("body"),d=a('<div id="sidenav-overlay"></div>'),j=c.innerWidth();c.css("overflow","hidden"),c.width(j),a("body").append(f),"left"===b.edge?(f.css({width:"50%",right:0,left:""}),e.velocity({translateX:[0,-1*b.menuWidth]},{duration:300,queue:!1,easing:"easeOutQuad"})):(f.css({width:"50%",right:"",left:0}),e.velocity({translateX:[0,b.menuWidth]},{duration:300,queue:!1,easing:"easeOutQuad"})),d.css("opacity",0).click(function(){i=!1,h=!1,g(),d.velocity({opacity:0},{duration:300,queue:!1,easing:"easeOutQuad",complete:function(){a(this).remove()}})}),a("body").append(d),d.velocity({opacity:1},{duration:300,queue:!1,easing:"easeOutQuad",complete:function(){i=!0,h=!1}})}return!1})})},destroy:function(){var b=a("#sidenav-overlay"),c=a('.drag-target[data-sidenav="'+a(this).attr("data-activates")+'"]');b.trigger("click"),c.remove(),a(this).off("click"),b.remove()},show:function(){this.trigger("click")},hide:function(){a("#sidenav-overlay").trigger("click")}};a.fn.sideNav=function(c){return b[c]?b[c].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof c&&c?void a.error("Method "+c+" does not exist on jQuery.sideNav"):b.init.apply(this,arguments)}}(jQuery),function(a){function b(b,c,d,e){var g=a();return a.each(f,function(a,f){if(f.height()>0){var h=f.offset().top,i=f.offset().left,j=i+f.width(),k=h+f.height(),l=!(i>c||j<e||h>d||k<b);l&&g.push(f)}}),g}function c(c){++i;var d=e.scrollTop(),f=e.scrollLeft(),h=f+e.width(),k=d+e.height(),l=b(d+j.top+c||200,h+j.right,k+j.bottom,f+j.left);a.each(l,function(a,b){var c=b.data("scrollSpy:ticks");"number"!=typeof c&&b.triggerHandler("scrollSpy:enter"),b.data("scrollSpy:ticks",i)}),a.each(g,function(a,b){var c=b.data("scrollSpy:ticks");"number"==typeof c&&c!==i&&(b.triggerHandler("scrollSpy:exit"),b.data("scrollSpy:ticks",null))}),g=l}function d(){e.trigger("scrollSpy:winSize")}var e=a(window),f=[],g=[],h=!1,i=0,j={top:0,right:0,bottom:0,left:0};a.scrollSpy=function(b,d){var g={throttle:100,scrollOffset:200};d=a.extend(g,d);var i=[];b=a(b),b.each(function(b,c){f.push(a(c)),a(c).data("scrollSpy:id",b),a('a[href="#'+a(c).attr("id")+'"]').click(function(b){b.preventDefault();var c=a(Materialize.escapeHash(this.hash)).offset().top+1;a("html, body").animate({scrollTop:c-d.scrollOffset},{duration:400,queue:!1,easing:"easeOutCubic"})})}),j.top=d.offsetTop||0,j.right=d.offsetRight||0,j.bottom=d.offsetBottom||0,j.left=d.offsetLeft||0;var k=Materialize.throttle(function(){c(d.scrollOffset)},d.throttle||100),l=function(){a(document).ready(k)};return h||(e.on("scroll",l),e.on("resize",l),h=!0),setTimeout(l,0),b.on("scrollSpy:enter",function(){i=a.grep(i,function(a){return 0!=a.height()});var b=a(this);i[0]?(a('a[href="#'+i[0].attr("id")+'"]').removeClass("active"),b.data("scrollSpy:id")<i[0].data("scrollSpy:id")?i.unshift(a(this)):i.push(a(this))):i.push(a(this)),a('a[href="#'+i[0].attr("id")+'"]').addClass("active")}),b.on("scrollSpy:exit",function(){if(i=a.grep(i,function(a){return 0!=a.height()}),i[0]){a('a[href="#'+i[0].attr("id")+'"]').removeClass("active");var b=a(this);i=a.grep(i,function(a){return a.attr("id")!=b.attr("id")}),i[0]&&a('a[href="#'+i[0].attr("id")+'"]').addClass("active")}}),b},a.winSizeSpy=function(b){return a.winSizeSpy=function(){return e},b=b||{throttle:100},e.on("resize",Materialize.throttle(d,b.throttle||100))},a.fn.scrollSpy=function(b){return a.scrollSpy(a(this),b)}}(jQuery),function(a){a(document).ready(function(){function b(b){var c=b.css("font-family"),d=b.css("font-size"),f=b.css("line-height");d&&e.css("font-size",d),c&&e.css("font-family",c),f&&e.css("line-height",f),"off"===b.attr("wrap")&&e.css("overflow-wrap","normal").css("white-space","pre"),e.text(b.val()+"\n");var g=e.html().replace(/\n/g,"<br>");e.html(g),b.is(":visible")?e.css("width",b.width()):e.css("width",a(window).width()/2),b.css("height",e.height())}Materialize.updateTextFields=function(){var b="input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search], textarea";a(b).each(function(b,c){var d=a(this);a(c).val().length>0||c.autofocus||void 0!==d.attr("placeholder")?d.siblings("label").addClass("active"):a(c)[0].validity?d.siblings("label").toggleClass("active",a(c)[0].validity.badInput===!0):d.siblings("label").removeClass("active")})};var c="input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search], textarea";a(document).on("change",c,function(){0===a(this).val().length&&void 0===a(this).attr("placeholder")||a(this).siblings("label").addClass("active"),validate_field(a(this))}),a(document).ready(function(){Materialize.updateTextFields()}),a(document).on("reset",function(b){var d=a(b.target);d.is("form")&&(d.find(c).removeClass("valid").removeClass("invalid"),d.find(c).each(function(){""===a(this).attr("value")&&a(this).siblings("label").removeClass("active")}),d.find("select.initialized").each(function(){var a=d.find("option[selected]").text();d.siblings("input.select-dropdown").val(a)}))}),a(document).on("focus",c,function(){a(this).siblings("label, .prefix").addClass("active")}),a(document).on("blur",c,function(){var b=a(this),c=".prefix";0===b.val().length&&b[0].validity.badInput!==!0&&void 0===b.attr("placeholder")&&(c+=", label"),b.siblings(c).removeClass("active"),validate_field(b)}),window.validate_field=function(a){var b=void 0!==a.attr("data-length"),c=parseInt(a.attr("data-length")),d=a.val().length;0===a.val().length&&a[0].validity.badInput===!1?a.hasClass("validate")&&(a.removeClass("valid"),a.removeClass("invalid")):a.hasClass("validate")&&(a.is(":valid")&&b&&d<=c||a.is(":valid")&&!b?(a.removeClass("invalid"),a.addClass("valid")):(a.removeClass("valid"),a.addClass("invalid")))};var d="input[type=radio], input[type=checkbox]";a(document).on("keyup.radio",d,function(b){if(9===b.which){a(this).addClass("tabbed");var c=a(this);return void c.one("blur",function(b){a(this).removeClass("tabbed")})}});var e=a(".hiddendiv").first();e.length||(e=a('<div class="hiddendiv common"></div>'),a("body").append(e));var f=".materialize-textarea";a(f).each(function(){var c=a(this);c.val().length&&b(c)}),a("body").on("keyup keydown autoresize",f,function(){b(a(this))}),a(document).on("change",'.file-field input[type="file"]',function(){for(var b=a(this).closest(".file-field"),c=b.find("input.file-path"),d=a(this)[0].files,e=[],f=0;f<d.length;f++)e.push(d[f].name);c.val(e.join(", ")),c.trigger("change")});var g,h="input[type=range]",i=!1;a(h).each(function(){var b=a('<span class="thumb"><span class="value"></span></span>');a(this).after(b)});var j=".range-field";a(document).on("change",h,function(b){var c=a(this).siblings(".thumb");c.find(".value").html(a(this).val())}),a(document).on("input mousedown touchstart",h,function(b){var c=a(this).siblings(".thumb"),d=a(this).outerWidth();c.length<=0&&(c=a('<span class="thumb"><span class="value"></span></span>'),a(this).after(c)),c.find(".value").html(a(this).val()),i=!0,a(this).addClass("active"),c.hasClass("active")||c.velocity({height:"30px",width:"30px",top:"-20px",marginLeft:"-15px"},{duration:300,easing:"easeOutExpo"}),"input"!==b.type&&(g=void 0===b.pageX||null===b.pageX?b.originalEvent.touches[0].pageX-a(this).offset().left:b.pageX-a(this).offset().left,g<0?g=0:g>d&&(g=d),c.addClass("active").css("left",g)),c.find(".value").html(a(this).val())}),a(document).on("mouseup touchend",j,function(){i=!1,a(this).removeClass("active")}),a(document).on("mousemove touchmove",j,function(b){var c,d=a(this).children(".thumb");if(i){d.hasClass("active")||d.velocity({height:"30px",width:"30px",top:"-20px",marginLeft:"-15px"},{duration:300,easing:"easeOutExpo"}),c=void 0===b.pageX||null===b.pageX?b.originalEvent.touches[0].pageX-a(this).offset().left:b.pageX-a(this).offset().left;var e=a(this).outerWidth();c<0?c=0:c>e&&(c=e),d.addClass("active").css("left",c),d.find(".value").html(d.siblings(h).val())}}),a(document).on("mouseout touchleave",j,function(){if(!i){var b=a(this).children(".thumb");b.hasClass("active")&&b.velocity({height:"0",width:"0",top:"10px",marginLeft:"-6px"},{duration:100}),b.removeClass("active")}}),a.fn.autocomplete=function(b){var c={data:{},limit:1/0,onAutocomplete:null};return b=a.extend(c,b),this.each(function(){var c,d=a(this),e=b.data,f=0,g=0,h=d.closest(".input-field");if(!a.isEmptyObject(e)){var i,j=a('<ul class="autocomplete-content dropdown-content"></ul>');h.length?(i=h.children(".autocomplete-content.dropdown-content").first(),i.length||h.append(j)):(i=d.next(".autocomplete-content.dropdown-content"),i.length||d.after(j)),i.length&&(j=i);var k=function(a,b){var c=b.find("img"),d=b.text().toLowerCase().indexOf(""+a.toLowerCase()),e=d+a.length-1,f=b.text().slice(0,d),g=b.text().slice(d,e+1),h=b.text().slice(e+1);b.html("<span>"+f+"<span class='highlight'>"+g+"</span>"+h+"</span>"),c.length&&b.prepend(c)},l=function(){g=0,j.find(".active").removeClass("active")};d.off("keyup.autocomplete").on("keyup.autocomplete",function(g){if(f=0,13!==g.which&&38!==g.which&&40!==g.which){var h=d.val().toLowerCase();if(c!==h&&(j.empty(),l(),""!==h))for(var i in e)if(e.hasOwnProperty(i)&&i.toLowerCase().indexOf(h)!==-1&&i.toLowerCase()!==h){if(f>=b.limit)break;var m=a("<li></li>");e[i]?m.append('<img src="'+e[i]+'" class="right circle"><span>'+i+"</span>"):m.append("<span>"+i+"</span>"),j.append(m),k(h,m),f++}c=h}}),d.off("keydown.autocomplete").on("keydown.autocomplete",function(a){var b,c=a.which,d=j.children("li").length,e=j.children(".active").first();return 13===c?(b=j.children("li").eq(g),void(b.length&&(b.click(),a.preventDefault()))):void(38!==c&&40!==c||(a.preventDefault(),38===c&&g>0&&g--,40===c&&g<d-1&&e.length&&g++,e.removeClass("active"),j.children("li").eq(g).addClass("active")))}),j.on("click","li",function(){var c=a(this).text().trim();d.val(c),d.trigger("change"),j.empty(),l(),"function"==typeof b.onAutocomplete&&b.onAutocomplete.call(this,c)})}})}}),a.fn.material_select=function(b){function c(a,b,c){var e=a.indexOf(b),f=e===-1;return f?a.push(b):a.splice(e,1),c.siblings("ul.dropdown-content").find("li").eq(b).toggleClass("active"),c.find("option").eq(b).prop("selected",f),d(a,c),f}function d(a,b){
+for(var c="",d=0,e=a.length;d<e;d++){var f=b.find("option").eq(a[d]).text();c+=0===d?f:", "+f}""===c&&(c=b.find("option:disabled").eq(0).text()),b.siblings("input.select-dropdown").val(c)}a(this).each(function(){var d=a(this);if(!d.hasClass("browser-default")){var e=!!d.attr("multiple"),f=d.data("select-id");if(f&&(d.parent().find("span.caret").remove(),d.parent().find("input").remove(),d.unwrap(),a("ul#select-options-"+f).remove()),"destroy"===b)return void d.data("select-id",null).removeClass("initialized");var g=Materialize.guid();d.data("select-id",g);var h=a('<div class="select-wrapper"></div>');h.addClass(d.attr("class"));var i=a('<ul id="select-options-'+g+'" class="dropdown-content select-dropdown '+(e?"multiple-select-dropdown":"")+'"></ul>'),j=d.children("option, optgroup"),k=[],l=!1,m=d.find("option:selected").html()||d.find("option:first").html()||"",n=function(b,c,d){var e=c.is(":disabled")?"disabled ":"",f="optgroup-option"===d?"optgroup-option ":"",g=c.data("icon"),h=c.attr("class");if(g){var j="";return h&&(j=' class="'+h+'"'),"multiple"===d?i.append(a('<li class="'+e+'"><img alt="" src="'+g+'"'+j+'><span><input type="checkbox"'+e+"/><label></label>"+c.html()+"</span></li>")):i.append(a('<li class="'+e+f+'"><img alt="" src="'+g+'"'+j+"><span>"+c.html()+"</span></li>")),!0}"multiple"===d?i.append(a('<li class="'+e+'"><span><input type="checkbox"'+e+"/><label></label>"+c.html()+"</span></li>")):i.append(a('<li class="'+e+f+'"><span>'+c.html()+"</span></li>"))};j.length&&j.each(function(){if(a(this).is("option"))e?n(d,a(this),"multiple"):n(d,a(this));else if(a(this).is("optgroup")){var b=a(this).children("option");i.append(a('<li class="optgroup"><span>'+a(this).attr("label")+"</span></li>")),b.each(function(){n(d,a(this),"optgroup-option")})}}),i.find("li:not(.optgroup)").each(function(f){a(this).click(function(g){if(!a(this).hasClass("disabled")&&!a(this).hasClass("optgroup")){var h=!0;e?(a('input[type="checkbox"]',this).prop("checked",function(a,b){return!b}),h=c(k,a(this).index(),d),q.trigger("focus")):(i.find("li").removeClass("active"),a(this).toggleClass("active"),q.val(a(this).text())),r(i,a(this)),d.find("option").eq(f).prop("selected",h),d.trigger("change"),"undefined"!=typeof b&&b()}g.stopPropagation()})}),d.wrap(h);var o=a('<span class="caret">&#9660;</span>');d.is(":disabled")&&o.addClass("disabled");var p=m.replace(/"/g,"&quot;"),q=a('<input type="text" class="select-dropdown" readonly="true" '+(d.is(":disabled")?"disabled":"")+' data-activates="select-options-'+g+'" value="'+p+'"/>');d.before(q),q.before(o),q.after(i),d.is(":disabled")||q.dropdown({hover:!1,closeOnClick:!1}),d.attr("tabindex")&&a(q[0]).attr("tabindex",d.attr("tabindex")),d.addClass("initialized"),q.on({focus:function(){if(a("ul.select-dropdown").not(i[0]).is(":visible")&&a("input.select-dropdown").trigger("close"),!i.is(":visible")){a(this).trigger("open",["focus"]);var b=a(this).val();e&&b.indexOf(",")>=0&&(b=b.split(",")[0]);var c=i.find("li").filter(function(){return a(this).text().toLowerCase()===b.toLowerCase()})[0];r(i,c,!0)}},click:function(a){a.stopPropagation()}}),q.on("blur",function(){e||a(this).trigger("close"),i.find("li.selected").removeClass("selected")}),i.hover(function(){l=!0},function(){l=!1}),a(window).on({click:function(){e&&(l||q.trigger("close"))}}),e&&d.find("option:selected:not(:disabled)").each(function(){var b=a(this).index();c(k,b,d),i.find("li").eq(b).find(":checkbox").prop("checked",!0)});var r=function(b,c,d){if(c){b.find("li.selected").removeClass("selected");var f=a(c);f.addClass("selected"),e&&!d||i.scrollTo(f)}},s=[],t=function(b){if(9==b.which)return void q.trigger("close");if(40==b.which&&!i.is(":visible"))return void q.trigger("open");if(13!=b.which||i.is(":visible")){b.preventDefault();var c=String.fromCharCode(b.which).toLowerCase(),d=[9,13,27,38,40];if(c&&d.indexOf(b.which)===-1){s.push(c);var f=s.join(""),g=i.find("li").filter(function(){return 0===a(this).text().toLowerCase().indexOf(f)})[0];g&&r(i,g)}if(13==b.which){var h=i.find("li.selected:not(.disabled)")[0];h&&(a(h).trigger("click"),e||q.trigger("close"))}40==b.which&&(g=i.find("li.selected").length?i.find("li.selected").next("li:not(.disabled)")[0]:i.find("li:not(.disabled)")[0],r(i,g)),27==b.which&&q.trigger("close"),38==b.which&&(g=i.find("li.selected").prev("li:not(.disabled)")[0],g&&r(i,g)),setTimeout(function(){s=[]},1e3)}};q.on("keydown",t)}})}}(jQuery),function(a){var b={init:function(b){var c={indicators:!0,height:400,transition:500,interval:6e3};return b=a.extend(c,b),this.each(function(){function c(a,b){a.hasClass("center-align")?a.velocity({opacity:0,translateY:-100},{duration:b,queue:!1}):a.hasClass("right-align")?a.velocity({opacity:0,translateX:100},{duration:b,queue:!1}):a.hasClass("left-align")&&a.velocity({opacity:0,translateX:-100},{duration:b,queue:!1})}function d(a){a>=j.length?a=0:a<0&&(a=j.length-1),k=i.find(".active").index(),k!=a&&(e=j.eq(k),$caption=e.find(".caption"),e.removeClass("active"),e.velocity({opacity:0},{duration:b.transition,queue:!1,easing:"easeOutQuad",complete:function(){j.not(".active").velocity({opacity:0,translateX:0,translateY:0},{duration:0,queue:!1})}}),c($caption,b.transition),b.indicators&&f.eq(k).removeClass("active"),j.eq(a).velocity({opacity:1},{duration:b.transition,queue:!1,easing:"easeOutQuad"}),j.eq(a).find(".caption").velocity({opacity:1,translateX:0,translateY:0},{duration:b.transition,delay:b.transition,queue:!1,easing:"easeOutQuad"}),j.eq(a).addClass("active"),b.indicators&&f.eq(a).addClass("active"))}var e,f,g,h=a(this),i=h.find("ul.slides").first(),j=i.find("> li"),k=i.find(".active").index();k!=-1&&(e=j.eq(k)),h.hasClass("fullscreen")||(b.indicators?h.height(b.height+40):h.height(b.height),i.height(b.height)),j.find(".caption").each(function(){c(a(this),0)}),j.find("img").each(function(){var b="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";a(this).attr("src")!==b&&(a(this).css("background-image","url("+a(this).attr("src")+")"),a(this).attr("src",b))}),b.indicators&&(f=a('<ul class="indicators"></ul>'),j.each(function(c){var e=a('<li class="indicator-item"></li>');e.click(function(){var c=i.parent(),e=c.find(a(this)).index();d(e),clearInterval(g),g=setInterval(function(){k=i.find(".active").index(),j.length==k+1?k=0:k+=1,d(k)},b.transition+b.interval)}),f.append(e)}),h.append(f),f=h.find("ul.indicators").find("li.indicator-item")),e?e.show():(j.first().addClass("active").velocity({opacity:1},{duration:b.transition,queue:!1,easing:"easeOutQuad"}),k=0,e=j.eq(k),b.indicators&&f.eq(k).addClass("active")),e.find("img").each(function(){e.find(".caption").velocity({opacity:1,translateX:0,translateY:0},{duration:b.transition,queue:!1,easing:"easeOutQuad"})}),g=setInterval(function(){k=i.find(".active").index(),d(k+1)},b.transition+b.interval);var l=!1,m=!1,n=!1;h.hammer({prevent_default:!1}).bind("pan",function(a){if("touch"===a.gesture.pointerType){clearInterval(g);var b=a.gesture.direction,c=a.gesture.deltaX,d=a.gesture.velocityX,e=a.gesture.velocityY;$curr_slide=i.find(".active"),Math.abs(d)>Math.abs(e)&&$curr_slide.velocity({translateX:c},{duration:50,queue:!1,easing:"easeOutQuad"}),4===b&&(c>h.innerWidth()/2||d<-.65)?n=!0:2===b&&(c<-1*h.innerWidth()/2||d>.65)&&(m=!0);var f;m&&(f=$curr_slide.next(),0===f.length&&(f=j.first()),f.velocity({opacity:1},{duration:300,queue:!1,easing:"easeOutQuad"})),n&&(f=$curr_slide.prev(),0===f.length&&(f=j.last()),f.velocity({opacity:1},{duration:300,queue:!1,easing:"easeOutQuad"}))}}).bind("panend",function(a){"touch"===a.gesture.pointerType&&($curr_slide=i.find(".active"),l=!1,curr_index=i.find(".active").index(),!n&&!m||j.length<=1?$curr_slide.velocity({translateX:0},{duration:300,queue:!1,easing:"easeOutQuad"}):m?(d(curr_index+1),$curr_slide.velocity({translateX:-1*h.innerWidth()},{duration:300,queue:!1,easing:"easeOutQuad",complete:function(){$curr_slide.velocity({opacity:0,translateX:0},{duration:0,queue:!1})}})):n&&(d(curr_index-1),$curr_slide.velocity({translateX:h.innerWidth()},{duration:300,queue:!1,easing:"easeOutQuad",complete:function(){$curr_slide.velocity({opacity:0,translateX:0},{duration:0,queue:!1})}})),m=!1,n=!1,clearInterval(g),g=setInterval(function(){k=i.find(".active").index(),j.length==k+1?k=0:k+=1,d(k)},b.transition+b.interval))}),h.on("sliderPause",function(){clearInterval(g)}),h.on("sliderStart",function(){clearInterval(g),g=setInterval(function(){k=i.find(".active").index(),j.length==k+1?k=0:k+=1,d(k)},b.transition+b.interval)}),h.on("sliderNext",function(){k=i.find(".active").index(),d(k+1)}),h.on("sliderPrev",function(){k=i.find(".active").index(),d(k-1)})})},pause:function(){a(this).trigger("sliderPause")},start:function(){a(this).trigger("sliderStart")},next:function(){a(this).trigger("sliderNext")},prev:function(){a(this).trigger("sliderPrev")}};a.fn.slider=function(c){return b[c]?b[c].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof c&&c?void a.error("Method "+c+" does not exist on jQuery.tooltip"):b.init.apply(this,arguments)}}(jQuery),function(a){a(document).ready(function(){a(document).on("click.card",".card",function(b){a(this).find("> .card-reveal").length&&(a(b.target).is(a(".card-reveal .card-title"))||a(b.target).is(a(".card-reveal .card-title i"))?a(this).find(".card-reveal").velocity({translateY:0},{duration:225,queue:!1,easing:"easeInOutQuad",complete:function(){a(this).css({display:"none"})}}):(a(b.target).is(a(".card .activator"))||a(b.target).is(a(".card .activator i")))&&(a(b.target).closest(".card").css("overflow","hidden"),a(this).find(".card-reveal").css({display:"block"}).velocity("stop",!1).velocity({translateY:"-100%"},{duration:300,queue:!1,easing:"easeInOutQuad"})))})})}(jQuery),function(a){var b={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteData:{},autocompleteLimit:1/0};a(document).ready(function(){a(document).on("click",".chip .close",function(b){var c=a(this).closest(".chips");c.attr("data-initialized")||a(this).closest(".chip").remove()})}),a.fn.material_chip=function(c){var d=this;if(this.$el=a(this),this.$document=a(document),this.SELS={CHIPS:".chips",CHIP:".chip",INPUT:"input",DELETE:".material-icons",SELECTED_CHIP:".selected"},"data"===c)return this.$el.data("chips");var e=a.extend({},b,c);d.hasAutocomplete=!a.isEmptyObject(e.autocompleteData),this.init=function(){var b=0;d.$el.each(function(){var c=a(this),f=Materialize.guid();d.chipId=f,e.data&&e.data instanceof Array||(e.data=[]),c.data("chips",e.data),c.attr("data-index",b),c.attr("data-initialized",!0),c.hasClass(d.SELS.CHIPS)||c.addClass("chips"),d.chips(c,f),b++})},this.handleEvents=function(){var b=d.SELS;d.$document.off("click.chips-focus",b.CHIPS).on("click.chips-focus",b.CHIPS,function(c){a(c.target).find(b.INPUT).focus()}),d.$document.off("click.chips-select",b.CHIP).on("click.chips-select",b.CHIP,function(c){var e=a(c.target);if(e.length){var f=e.hasClass("selected"),g=e.closest(b.CHIPS);a(b.CHIP).removeClass("selected"),f||d.selectChip(e.index(),g)}}),d.$document.off("keydown.chips").on("keydown.chips",function(c){if(!a(c.target).is("input, textarea")){var e,f=d.$document.find(b.CHIP+b.SELECTED_CHIP),g=f.closest(b.CHIPS),h=f.siblings(b.CHIP).length;if(f.length)if(8===c.which||46===c.which){c.preventDefault(),e=f.index(),d.deleteChip(e,g);var i=null;e+1<h?i=e:e!==h&&e+1!==h||(i=h-1),i<0&&(i=null),null!==i&&d.selectChip(i,g),h||g.find("input").focus()}else if(37===c.which){if(e=f.index()-1,e<0)return;a(b.CHIP).removeClass("selected"),d.selectChip(e,g)}else if(39===c.which){if(e=f.index()+1,a(b.CHIP).removeClass("selected"),e>h)return void g.find("input").focus();d.selectChip(e,g)}}}),d.$document.off("focusin.chips",b.CHIPS+" "+b.INPUT).on("focusin.chips",b.CHIPS+" "+b.INPUT,function(c){var d=a(c.target).closest(b.CHIPS);d.addClass("focus"),d.siblings("label, .prefix").addClass("active"),a(b.CHIP).removeClass("selected")}),d.$document.off("focusout.chips",b.CHIPS+" "+b.INPUT).on("focusout.chips",b.CHIPS+" "+b.INPUT,function(c){var d=a(c.target).closest(b.CHIPS);d.removeClass("focus"),d.data("chips").length||d.siblings("label").removeClass("active"),d.siblings(".prefix").removeClass("active")}),d.$document.off("keydown.chips-add",b.CHIPS+" "+b.INPUT).on("keydown.chips-add",b.CHIPS+" "+b.INPUT,function(c){var e=a(c.target),f=e.closest(b.CHIPS),g=f.children(b.CHIP).length;if(13===c.which){if(d.hasAutocomplete&&f.find(".autocomplete-content.dropdown-content").length&&f.find(".autocomplete-content.dropdown-content").children().length)return;return c.preventDefault(),d.addChip({tag:e.val()},f),void e.val("")}if((8===c.keyCode||37===c.keyCode)&&""===e.val()&&g)return c.preventDefault(),d.selectChip(g-1,f),void e.blur()}),d.$document.off("click.chips-delete",b.CHIPS+" "+b.DELETE).on("click.chips-delete",b.CHIPS+" "+b.DELETE,function(c){var e=a(c.target),f=e.closest(b.CHIPS),g=e.closest(b.CHIP);c.stopPropagation(),d.deleteChip(g.index(),f),f.find("input").focus()})},this.chips=function(b,c){var f="";b.data("chips").forEach(function(a){f+=d.renderChip(a)}),f+='<input id="'+c+'" class="input" placeholder="">',b.html(f),d.setPlaceholder(b);var g=b.next("label");g.length&&(g.attr("for",c),b.data("chips").length&&g.addClass("active"));var h=a("#"+c);d.hasAutocomplete&&h.autocomplete({data:e.autocompleteData,limit:e.autocompleteLimit,onAutocomplete:function(a){d.addChip({tag:a},b),h.val(""),h.focus()}})},this.renderChip=function(a){if(a.tag){var b='<div class="chip">'+a.tag;return a.image&&(b+=' <img src="'+a.image+'"> '),b+='<i class="material-icons close">close</i>',b+="</div>"}},this.setPlaceholder=function(a){a.data("chips").length&&e.placeholder?a.find("input").prop("placeholder",e.placeholder):!a.data("chips").length&&e.secondaryPlaceholder&&a.find("input").prop("placeholder",e.secondaryPlaceholder)},this.isValid=function(a,b){for(var c=a.data("chips"),d=!1,e=0;e<c.length;e++)if(c[e].tag===b.tag)return void(d=!0);return""!==b.tag&&!d},this.addChip=function(b,c){if(d.isValid(c,b)){for(var e=d.renderChip(b),f=[],g=c.data("chips"),h=0;h<g.length;h++)f.push(g[h]);f.push(b),c.data("chips",f),a(e).insertBefore(c.find("input")),c.trigger("chip.add",b),d.setPlaceholder(c)}},this.deleteChip=function(a,b){var c=b.data("chips")[a];b.find(".chip").eq(a).remove();for(var e=[],f=b.data("chips"),g=0;g<f.length;g++)g!==a&&e.push(f[g]);b.data("chips",e),b.trigger("chip.delete",c),d.setPlaceholder(b)},this.selectChip=function(a,b){var c=b.find(".chip").eq(a);c&&!1===c.hasClass("selected")&&(c.addClass("selected"),b.trigger("chip.select",b.data("chips")[a]))},this.getChipsElement=function(a,b){return b.eq(a)},this.init(),this.handleEvents()}}(jQuery),function(a){a.fn.pushpin=function(b){var c={top:0,bottom:1/0,offset:0};return"remove"===b?(this.each(function(){(id=a(this).data("pushpin-id"))&&(a(window).off("scroll."+id),a(this).removeData("pushpin-id").removeClass("pin-top pinned pin-bottom").removeAttr("style"))}),!1):(b=a.extend(c,b),$index=0,this.each(function(){function c(a){a.removeClass("pin-top"),a.removeClass("pinned"),a.removeClass("pin-bottom")}function d(d,e){d.each(function(){b.top<=e&&b.bottom>=e&&!a(this).hasClass("pinned")&&(c(a(this)),a(this).css("top",b.offset),a(this).addClass("pinned")),e<b.top&&!a(this).hasClass("pin-top")&&(c(a(this)),a(this).css("top",0),a(this).addClass("pin-top")),e>b.bottom&&!a(this).hasClass("pin-bottom")&&(c(a(this)),a(this).addClass("pin-bottom"),a(this).css("top",b.bottom-g))})}var e=Materialize.guid(),f=a(this),g=a(this).offset().top;a(this).data("pushpin-id",e),d(f,a(window).scrollTop()),a(window).on("scroll."+e,function(){var c=a(window).scrollTop()+b.offset;d(f,c)})}))}}(jQuery),function(a){a(document).ready(function(){a.fn.reverse=[].reverse,a(document).on("mouseenter.fixedActionBtn",".fixed-action-btn:not(.click-to-toggle):not(.toolbar)",function(c){var d=a(this);b(d)}),a(document).on("mouseleave.fixedActionBtn",".fixed-action-btn:not(.click-to-toggle):not(.toolbar)",function(b){var d=a(this);c(d)}),a(document).on("click.fabClickToggle",".fixed-action-btn.click-to-toggle > a",function(d){var e=a(this),f=e.parent();f.hasClass("active")?c(f):b(f)}),a(document).on("click.fabToolbar",".fixed-action-btn.toolbar > a",function(b){var c=a(this),e=c.parent();d(e)})}),a.fn.extend({openFAB:function(){b(a(this))},closeFAB:function(){c(a(this))},openToolbar:function(){d(a(this))},closeToolbar:function(){e(a(this))}});var b=function(b){var c=b;if(c.hasClass("active")===!1){var d,e,f=c.hasClass("horizontal");f===!0?e=40:d=40,c.addClass("active"),c.find("ul .btn-floating").velocity({scaleY:".4",scaleX:".4",translateY:d+"px",translateX:e+"px"},{duration:0});var g=0;c.find("ul .btn-floating").reverse().each(function(){a(this).velocity({opacity:"1",scaleX:"1",scaleY:"1",translateY:"0",translateX:"0"},{duration:80,delay:g}),g+=40})}},c=function(a){var b,c,d=a,e=d.hasClass("horizontal");e===!0?c=40:b=40,d.removeClass("active");d.find("ul .btn-floating").velocity("stop",!0),d.find("ul .btn-floating").velocity({opacity:"0",scaleX:".4",scaleY:".4",translateY:b+"px",translateX:c+"px"},{duration:80})},d=function(b){if("true"!==b.attr("data-open")){var c,d,f,g=window.innerWidth,h=window.innerHeight,i=b[0].getBoundingClientRect(),j=b.find("> a").first(),k=b.find("> ul").first(),l=a('<div class="fab-backdrop"></div>'),m=j.css("background-color");j.append(l),c=i.left-g/2+i.width/2,d=h-i.bottom,f=g/l.width(),b.attr("data-origin-bottom",i.bottom),b.attr("data-origin-left",i.left),b.attr("data-origin-width",i.width),b.addClass("active"),b.attr("data-open",!0),b.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+c+"px)",transition:"none"}),j.css({transform:"translateY("+-d+"px)",transition:"none"}),l.css({"background-color":m}),setTimeout(function(){b.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),j.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){b.css({overflow:"hidden","background-color":m}),l.css({transform:"scale("+f+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),k.find("> li > a").css({opacity:1}),a(window).on("scroll.fabToolbarClose",function(){e(b),a(window).off("scroll.fabToolbarClose"),a(document).off("click.fabToolbarClose")}),a(document).on("click.fabToolbarClose",function(c){a(c.target).closest(k).length||(e(b),a(window).off("scroll.fabToolbarClose"),a(document).off("click.fabToolbarClose"))})},100)},0)}},e=function(a){if("true"===a.attr("data-open")){var b,c,d,e=window.innerWidth,f=window.innerHeight,g=a.attr("data-origin-width"),h=a.attr("data-origin-bottom"),i=a.attr("data-origin-left"),j=a.find("> .btn-floating").first(),k=a.find("> ul").first(),l=a.find(".fab-backdrop"),m=j.css("background-color");b=i-e/2+g/2,c=f-h,d=e/l.width(),a.removeClass("active"),a.attr("data-open",!1),a.css({"background-color":"transparent",transition:"none"}),j.css({transition:"none"}),l.css({transform:"scale(0)","background-color":m}),k.find("> li > a").css({opacity:""}),setTimeout(function(){l.remove(),a.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-b+"px,0,0)"}),j.css({overflow:"",transform:"translate3d(0,"+c+"px,0)"}),setTimeout(function(){a.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),j.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}}(jQuery),function(a){Materialize.fadeInImage=function(b){var c;if("string"==typeof b)c=a(b);else{if("object"!=typeof b)return;c=b}c.css({opacity:0}),a(c).velocity({opacity:1},{duration:650,queue:!1,easing:"easeOutSine"}),a(c).velocity({opacity:1},{duration:1300,queue:!1,easing:"swing",step:function(b,c){c.start=100;var d=b/100,e=150-(100-b)/1.75;e<100&&(e=100),b>=0&&a(this).css({"-webkit-filter":"grayscale("+d+")brightness("+e+"%)",filter:"grayscale("+d+")brightness("+e+"%)"})}})},Materialize.showStaggeredList=function(b){var c;if("string"==typeof b)c=a(b);else{if("object"!=typeof b)return;c=b}var d=0;c.find("li").velocity({translateX:"-100px"},{duration:0}),c.find("li").each(function(){a(this).velocity({opacity:"1",translateX:"0"},{duration:800,delay:d,easing:[60,10]}),d+=120})},a(document).ready(function(){var b=!1,c=!1;a(".dismissable").each(function(){a(this).hammer({prevent_default:!1}).bind("pan",function(d){if("touch"===d.gesture.pointerType){var e=a(this),f=d.gesture.direction,g=d.gesture.deltaX,h=d.gesture.velocityX;e.velocity({translateX:g},{duration:50,queue:!1,easing:"easeOutQuad"}),4===f&&(g>e.innerWidth()/2||h<-.75)&&(b=!0),2===f&&(g<-1*e.innerWidth()/2||h>.75)&&(c=!0)}}).bind("panend",function(d){if(Math.abs(d.gesture.deltaX)<a(this).innerWidth()/2&&(c=!1,b=!1),"touch"===d.gesture.pointerType){var e=a(this);if(b||c){var f;f=b?e.innerWidth():-1*e.innerWidth(),e.velocity({translateX:f},{duration:100,queue:!1,easing:"easeOutQuad",complete:function(){e.css("border","none"),e.velocity({height:0,padding:0},{duration:200,queue:!1,easing:"easeOutQuad",complete:function(){e.remove()}})}})}else e.velocity({translateX:0},{duration:100,queue:!1,easing:"easeOutQuad"});b=!1,c=!1}})})})}(jQuery),function(a){var b=!1;Materialize.scrollFire=function(a){var c=function(){for(var b=window.pageYOffset+window.innerHeight,c=0;c<a.length;c++){var d=a[c],e=d.selector,f=d.offset,g=d.callback,h=document.querySelector(e);if(null!==h){var i=h.getBoundingClientRect().top+window.pageYOffset;if(b>i+f&&d.done!==!0){if("function"==typeof g)g.call(this,h);else if("string"==typeof g){var j=new Function(g);j(h)}d.done=!0}}}},d=Materialize.throttle(function(){c()},a.throttle||100);b||(window.addEventListener("scroll",d),window.addEventListener("resize",d),b=!0),setTimeout(d,0)}}(jQuery),function(a){"function"==typeof define&&define.amd?define("picker",["jquery"],a):"object"==typeof exports?module.exports=a(require("jquery")):this.Picker=a(jQuery)}(function(a){function b(f,g,i,l){function m(){return b._.node("div",b._.node("div",b._.node("div",b._.node("div",y.component.nodes(t.open),v.box),v.wrap),v.frame),v.holder)}function n(){w.data(g,y).addClass(v.input).attr("tabindex",-1).val(w.data("value")?y.get("select",u.format):f.value),u.editable||w.on("focus."+t.id+" click."+t.id,function(a){a.preventDefault(),y.$root.eq(0).focus()}).on("keydown."+t.id,q),e(f,{haspopup:!0,expanded:!1,readonly:!1,owns:f.id+"_root"})}function o(){y.$root.on({keydown:q,focusin:function(a){y.$root.removeClass(v.focused),a.stopPropagation()},"mousedown click":function(b){var c=b.target;c!=y.$root.children()[0]&&(b.stopPropagation(),"mousedown"!=b.type||a(c).is("input, select, textarea, button, option")||(b.preventDefault(),y.$root.eq(0).focus()))}}).on({focus:function(){w.addClass(v.target)},blur:function(){w.removeClass(v.target)}}).on("focus.toOpen",r).on("click","[data-pick], [data-nav], [data-clear], [data-close]",function(){var b=a(this),c=b.data(),d=b.hasClass(v.navDisabled)||b.hasClass(v.disabled),e=h();e=e&&(e.type||e.href),(d||e&&!a.contains(y.$root[0],e))&&y.$root.eq(0).focus(),!d&&c.nav?y.set("highlight",y.component.item.highlight,{nav:c.nav}):!d&&"pick"in c?y.set("select",c.pick):c.clear?y.clear().close(!0):c.close&&y.close(!0)}),e(y.$root[0],"hidden",!0)}function p(){var b;u.hiddenName===!0?(b=f.name,f.name=""):(b=["string"==typeof u.hiddenPrefix?u.hiddenPrefix:"","string"==typeof u.hiddenSuffix?u.hiddenSuffix:"_submit"],b=b[0]+f.name+b[1]),y._hidden=a('<input type=hidden name="'+b+'"'+(w.data("value")||f.value?' value="'+y.get("select",u.formatSubmit)+'"':"")+">")[0],w.on("change."+t.id,function(){y._hidden.value=f.value?y.get("select",u.formatSubmit):""}),u.container?a(u.container).append(y._hidden):w.after(y._hidden)}function q(a){var b=a.keyCode,c=/^(8|46)$/.test(b);return 27==b?(y.close(),!1):void((32==b||c||!t.open&&y.component.key[b])&&(a.preventDefault(),a.stopPropagation(),c?y.clear().close():y.open()))}function r(a){a.stopPropagation(),"focus"==a.type&&y.$root.addClass(v.focused),y.open()}if(!f)return b;var s=!1,t={id:f.id||"P"+Math.abs(~~(Math.random()*new Date))},u=i?a.extend(!0,{},i.defaults,l):l||{},v=a.extend({},b.klasses(),u.klass),w=a(f),x=function(){return this.start()},y=x.prototype={constructor:x,$node:w,start:function(){return t&&t.start?y:(t.methods={},t.start=!0,t.open=!1,t.type=f.type,f.autofocus=f==h(),f.readOnly=!u.editable,f.id=f.id||t.id,"text"!=f.type&&(f.type="text"),y.component=new i(y,u),y.$root=a(b._.node("div",m(),v.picker,'id="'+f.id+'_root" tabindex="0"')),o(),u.formatSubmit&&p(),n(),u.container?a(u.container).append(y.$root):w.after(y.$root),y.on({start:y.component.onStart,render:y.component.onRender,stop:y.component.onStop,open:y.component.onOpen,close:y.component.onClose,set:y.component.onSet}).on({start:u.onStart,render:u.onRender,stop:u.onStop,open:u.onOpen,close:u.onClose,set:u.onSet}),s=c(y.$root.children()[0]),f.autofocus&&y.open(),y.trigger("start").trigger("render"))},render:function(a){return a?y.$root.html(m()):y.$root.find("."+v.box).html(y.component.nodes(t.open)),y.trigger("render")},stop:function(){return t.start?(y.close(),y._hidden&&y._hidden.parentNode.removeChild(y._hidden),y.$root.remove(),w.removeClass(v.input).removeData(g),setTimeout(function(){w.off("."+t.id)},0),f.type=t.type,f.readOnly=!1,y.trigger("stop"),t.methods={},t.start=!1,y):y},open:function(c){return t.open?y:(w.addClass(v.active),e(f,"expanded",!0),setTimeout(function(){y.$root.addClass(v.opened),e(y.$root[0],"hidden",!1)},0),c!==!1&&(t.open=!0,s&&k.css("overflow","hidden").css("padding-right","+="+d()),y.$root.eq(0).focus(),j.on("click."+t.id+" focusin."+t.id,function(a){var b=a.target;b!=f&&b!=document&&3!=a.which&&y.close(b===y.$root.children()[0])}).on("keydown."+t.id,function(c){var d=c.keyCode,e=y.component.key[d],f=c.target;27==d?y.close(!0):f!=y.$root[0]||!e&&13!=d?a.contains(y.$root[0],f)&&13==d&&(c.preventDefault(),f.click()):(c.preventDefault(),e?b._.trigger(y.component.key.go,y,[b._.trigger(e)]):y.$root.find("."+v.highlighted).hasClass(v.disabled)||y.set("select",y.component.item.highlight).close())})),y.trigger("open"))},close:function(a){return a&&(y.$root.off("focus.toOpen").eq(0).focus(),setTimeout(function(){y.$root.on("focus.toOpen",r)},0)),w.removeClass(v.active),e(f,"expanded",!1),setTimeout(function(){y.$root.removeClass(v.opened+" "+v.focused),e(y.$root[0],"hidden",!0)},0),t.open?(t.open=!1,s&&k.css("overflow","").css("padding-right","-="+d()),j.off("."+t.id),y.trigger("close")):y},clear:function(a){return y.set("clear",null,a)},set:function(b,c,d){var e,f,g=a.isPlainObject(b),h=g?b:{};if(d=g&&a.isPlainObject(c)?c:d||{},b){g||(h[b]=c);for(e in h)f=h[e],e in y.component.item&&(void 0===f&&(f=null),y.component.set(e,f,d)),"select"!=e&&"clear"!=e||w.val("clear"==e?"":y.get(e,u.format)).trigger("change");y.render()}return d.muted?y:y.trigger("set",h)},get:function(a,c){if(a=a||"value",null!=t[a])return t[a];if("valueSubmit"==a){if(y._hidden)return y._hidden.value;a="value"}if("value"==a)return f.value;if(a in y.component.item){if("string"==typeof c){var d=y.component.get(a);return d?b._.trigger(y.component.formats.toString,y.component,[c,d]):""}return y.component.get(a)}},on:function(b,c,d){var e,f,g=a.isPlainObject(b),h=g?b:{};if(b){g||(h[b]=c);for(e in h)f=h[e],d&&(e="_"+e),t.methods[e]=t.methods[e]||[],t.methods[e].push(f)}return y},off:function(){var a,b,c=arguments;for(a=0,namesCount=c.length;a<namesCount;a+=1)b=c[a],b in t.methods&&delete t.methods[b];return y},trigger:function(a,c){var d=function(a){var d=t.methods[a];d&&d.map(function(a){b._.trigger(a,y,[c])})};return d("_"+a),d(a),y}};return new x}function c(a){var b,c="position";return a.currentStyle?b=a.currentStyle[c]:window.getComputedStyle&&(b=getComputedStyle(a)[c]),"fixed"==b}function d(){if(k.height()<=i.height())return 0;var b=a('<div style="visibility:hidden;width:100px" />').appendTo("body"),c=b[0].offsetWidth;b.css("overflow","scroll");var d=a('<div style="width:100%" />').appendTo(b),e=d[0].offsetWidth;return b.remove(),c-e}function e(b,c,d){if(a.isPlainObject(c))for(var e in c)f(b,e,c[e]);else f(b,c,d)}function f(a,b,c){a.setAttribute(("role"==b?"":"aria-")+b,c)}function g(b,c){a.isPlainObject(b)||(b={attribute:c}),c="";for(var d in b){var e=("role"==d?"":"aria-")+d,f=b[d];c+=null==f?"":e+'="'+b[d]+'"'}return c}function h(){try{return document.activeElement}catch(a){}}var i=a(window),j=a(document),k=a(document.documentElement);return b.klasses=function(a){return a=a||"picker",{picker:a,opened:a+"--opened",focused:a+"--focused",input:a+"__input",active:a+"__input--active",target:a+"__input--target",holder:a+"__holder",frame:a+"__frame",wrap:a+"__wrap",box:a+"__box"}},b._={group:function(a){for(var c,d="",e=b._.trigger(a.min,a);e<=b._.trigger(a.max,a,[e]);e+=a.i)c=b._.trigger(a.item,a,[e]),d+=b._.node(a.node,c[0],c[1],c[2]);return d},node:function(b,c,d,e){return c?(c=a.isArray(c)?c.join(""):c,d=d?' class="'+d+'"':"",e=e?" "+e:"","<"+b+d+e+">"+c+"</"+b+">"):""},lead:function(a){return(a<10?"0":"")+a},trigger:function(a,b,c){return"function"==typeof a?a.apply(b,c||[]):a},digits:function(a){return/\d/.test(a[1])?2:1},isDate:function(a){return{}.toString.call(a).indexOf("Date")>-1&&this.isInteger(a.getDate())},isInteger:function(a){return{}.toString.call(a).indexOf("Number")>-1&&a%1===0},ariaAttr:g},b.extend=function(c,d){a.fn[c]=function(e,f){var g=this.data(c);return"picker"==e?g:g&&"string"==typeof e?b._.trigger(g[e],g,[f]):this.each(function(){var f=a(this);f.data(c)||new b(this,c,d,e)})},a.fn[c].defaults=d.defaults},b}),function(a){"function"==typeof define&&define.amd?define(["picker","jquery"],a):"object"==typeof exports?module.exports=a(require("./picker.js"),require("jquery")):a(Picker,jQuery)}(function(a,b){function c(a,b){var c=this,d=a.$node[0],e=d.value,f=a.$node.data("value"),g=f||e,h=f?b.formatSubmit:b.format,i=function(){return d.currentStyle?"rtl"==d.currentStyle.direction:"rtl"==getComputedStyle(a.$root[0]).direction};c.settings=b,c.$node=a.$node,c.queue={min:"measure create",max:"measure create",now:"now create",select:"parse create validate",highlight:"parse navigate create validate",view:"parse create validate viewset",disable:"deactivate",enable:"activate"},c.item={},c.item.clear=null,c.item.disable=(b.disable||[]).slice(0),c.item.enable=-function(a){return a[0]===!0?a.shift():-1}(c.item.disable),c.set("min",b.min).set("max",b.max).set("now"),g?c.set("select",g,{format:h}):c.set("select",null).set("highlight",c.item.now),c.key={40:7,38:-7,39:function(){return i()?-1:1},37:function(){return i()?1:-1},go:function(a){var b=c.item.highlight,d=new Date(b.year,b.month,b.date+a);c.set("highlight",d,{interval:a}),this.render()}},a.on("render",function(){a.$root.find("."+b.klass.selectMonth).on("change",function(){var c=this.value;c&&(a.set("highlight",[a.get("view").year,c,a.get("highlight").date]),a.$root.find("."+b.klass.selectMonth).trigger("focus"))}),a.$root.find("."+b.klass.selectYear).on("change",function(){var c=this.value;c&&(a.set("highlight",[c,a.get("view").month,a.get("highlight").date]),a.$root.find("."+b.klass.selectYear).trigger("focus"))})},1).on("open",function(){var d="";c.disabled(c.get("now"))&&(d=":not(."+b.klass.buttonToday+")"),a.$root.find("button"+d+", select").attr("disabled",!1)},1).on("close",function(){a.$root.find("button, select").attr("disabled",!0)},1)}var d=7,e=6,f=a._;c.prototype.set=function(a,b,c){var d=this,e=d.item;return null===b?("clear"==a&&(a="select"),e[a]=b,d):(e["enable"==a?"disable":"flip"==a?"enable":a]=d.queue[a].split(" ").map(function(e){return b=d[e](a,b,c)}).pop(),"select"==a?d.set("highlight",e.select,c):"highlight"==a?d.set("view",e.highlight,c):a.match(/^(flip|min|max|disable|enable)$/)&&(e.select&&d.disabled(e.select)&&d.set("select",e.select,c),e.highlight&&d.disabled(e.highlight)&&d.set("highlight",e.highlight,c)),d)},c.prototype.get=function(a){return this.item[a]},c.prototype.create=function(a,c,d){var e,g=this;return c=void 0===c?a:c,
+c==-(1/0)||c==1/0?e=c:b.isPlainObject(c)&&f.isInteger(c.pick)?c=c.obj:b.isArray(c)?(c=new Date(c[0],c[1],c[2]),c=f.isDate(c)?c:g.create().obj):c=f.isInteger(c)||f.isDate(c)?g.normalize(new Date(c),d):g.now(a,c,d),{year:e||c.getFullYear(),month:e||c.getMonth(),date:e||c.getDate(),day:e||c.getDay(),obj:e||c,pick:e||c.getTime()}},c.prototype.createRange=function(a,c){var d=this,e=function(a){return a===!0||b.isArray(a)||f.isDate(a)?d.create(a):a};return f.isInteger(a)||(a=e(a)),f.isInteger(c)||(c=e(c)),f.isInteger(a)&&b.isPlainObject(c)?a=[c.year,c.month,c.date+a]:f.isInteger(c)&&b.isPlainObject(a)&&(c=[a.year,a.month,a.date+c]),{from:e(a),to:e(c)}},c.prototype.withinRange=function(a,b){return a=this.createRange(a.from,a.to),b.pick>=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b,c){return b=new Date,c&&c.rel&&b.setDate(b.getDate()+c.rel),this.normalize(b,c)},c.prototype.navigate=function(a,c,d){var e,f,g,h,i=b.isArray(c),j=b.isPlainObject(c),k=this.item.view;if(i||j){for(j?(f=c.year,g=c.month,h=c.date):(f=+c[0],g=+c[1],h=+c[2]),d&&d.nav&&k&&k.month!==g&&(f=k.year,g=k.month),e=new Date(f,g+(d&&d.nav?d.nav:0),1),f=e.getFullYear(),g=e.getMonth();new Date(f,g,h).getMonth()!==g;)h-=1;c=[f,g,h]}return c},c.prototype.normalize=function(a){return a.setHours(0,0,0,0),a},c.prototype.measure=function(a,b){var c=this;return b?"string"==typeof b?b=c.parse(a,b):f.isInteger(b)&&(b=c.now(a,b,{rel:b})):b="min"==a?-(1/0):1/0,b},c.prototype.viewset=function(a,b){return this.create([b.year,b.month,1])},c.prototype.validate=function(a,c,d){var e,g,h,i,j=this,k=c,l=d&&d.interval?d.interval:1,m=j.item.enable===-1,n=j.item.min,o=j.item.max,p=m&&j.item.disable.filter(function(a){if(b.isArray(a)){var d=j.create(a).pick;d<c.pick?e=!0:d>c.pick&&(g=!0)}return f.isInteger(a)}).length;if((!d||!d.nav)&&(!m&&j.disabled(c)||m&&j.disabled(c)&&(p||e||g)||!m&&(c.pick<=n.pick||c.pick>=o.pick)))for(m&&!p&&(!g&&l>0||!e&&l<0)&&(l*=-1);j.disabled(c)&&(Math.abs(l)>1&&(c.month<k.month||c.month>k.month)&&(c=k,l=l>0?1:-1),c.pick<=n.pick?(h=!0,l=1,c=j.create([n.year,n.month,n.date+(c.pick===n.pick?0:-1)])):c.pick>=o.pick&&(i=!0,l=-1,c=j.create([o.year,o.month,o.date+(c.pick===o.pick?0:1)])),!h||!i);)c=j.create([c.year,c.month,c.date+l]);return c},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return f.isInteger(d)?a.day===(c.settings.firstDay?d:d-1)%7:b.isArray(d)||f.isDate(d)?a.pick===c.create(d).pick:b.isPlainObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[3]||b.isPlainObject(a)&&a.inverted}).length,c.item.enable===-1?!d:d||a.pick<c.item.min.pick||a.pick>c.item.max.pick},c.prototype.parse=function(a,b,c){var d=this,e={};return b&&"string"==typeof b?(c&&c.format||(c=c||{},c.format=d.settings.format),d.formats.toArray(c.format).map(function(a){var c=d.formats[a],g=c?f.trigger(c,d,[b,e]):a.replace(/^!/,"").length;c&&(e[a]=b.substr(0,g)),b=b.substr(g)}),[e.yyyy||e.yy,+(e.mm||e.m)-1,e.dd||e.d]):b},c.prototype.formats=function(){function a(a,b,c){var d=a.match(/\w+/)[0];return c.mm||c.m||(c.m=b.indexOf(d)+1),d.length}function b(a){return a.match(/\w+/)[0].length}return{d:function(a,b){return a?f.digits(a):b.date},dd:function(a,b){return a?2:f.lead(b.date)},ddd:function(a,c){return a?b(a):this.settings.weekdaysShort[c.day]},dddd:function(a,c){return a?b(a):this.settings.weekdaysFull[c.day]},m:function(a,b){return a?f.digits(a):b.month+1},mm:function(a,b){return a?2:f.lead(b.month+1)},mmm:function(b,c){var d=this.settings.monthsShort;return b?a(b,d,c):d[c.month]},mmmm:function(b,c){var d=this.settings.monthsFull;return b?a(b,d,c):d[c.month]},yy:function(a,b){return a?2:(""+b.year).slice(2)},yyyy:function(a,b){return a?4:b.year},toArray:function(a){return a.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return f.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}}}(),c.prototype.isDateExact=function(a,c){var d=this;return f.isInteger(a)&&f.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(f.isDate(a)||b.isArray(a))&&(f.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:!(!b.isPlainObject(a)||!b.isPlainObject(c))&&(d.isDateExact(a.from,c.from)&&d.isDateExact(a.to,c.to))},c.prototype.isDateOverlap=function(a,c){var d=this,e=d.settings.firstDay?1:0;return f.isInteger(a)&&(f.isDate(c)||b.isArray(c))?(a=a%7+e,a===d.create(c).day+1):f.isInteger(c)&&(f.isDate(a)||b.isArray(a))?(c=c%7+e,c===d.create(a).day+1):!(!b.isPlainObject(a)||!b.isPlainObject(c))&&d.overlapRanges(a,c)},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(b.enable==-1?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,g=0;g<e.length;g+=1)if(d.isDateExact(a,e[g])){c=!0;break}c||(f.isInteger(a)||f.isDate(a)||b.isArray(a)||b.isPlainObject(a)&&a.from&&a.to)&&e.push(a)}),e},c.prototype.activate=function(a,c){var d=this,e=d.item.disable,g=e.length;return"flip"==c?d.flipEnable():c===!0?(d.flipEnable(1),e=[]):c===!1?(d.flipEnable(-1),e=[]):c.map(function(a){var c,h,i,j;for(i=0;i<g;i+=1){if(h=e[i],d.isDateExact(h,a)){c=e[i]=null,j=!0;break}if(d.isDateOverlap(h,a)){b.isPlainObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[3]||c.push("inverted")):f.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;i<g;i+=1)if(d.isDateExact(e[i],a)){e[i]=null;break}if(j)for(i=0;i<g;i+=1)if(d.isDateOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.nodes=function(a){var b=this,c=b.settings,g=b.item,h=g.now,i=g.select,j=g.highlight,k=g.view,l=g.disable,m=g.min,n=g.max,o=function(a,b){return c.firstDay&&(a.push(a.shift()),b.push(b.shift())),f.node("thead",f.node("tr",f.group({min:0,max:d-1,i:1,node:"th",item:function(d){return[a[d],c.klass.weekdays,'scope=col title="'+b[d]+'"']}})))}((c.showWeekdaysFull?c.weekdaysFull:c.weekdaysLetter).slice(0),c.weekdaysFull.slice(0)),p=function(a){return f.node("div"," ",c.klass["nav"+(a?"Next":"Prev")]+(a&&k.year>=n.year&&k.month>=n.month||!a&&k.year<=m.year&&k.month<=m.month?" "+c.klass.navDisabled:""),"data-nav="+(a||-1)+" "+f.ariaAttr({role:"button",controls:b.$node[0].id+"_table"})+' title="'+(a?c.labelMonthNext:c.labelMonthPrev)+'"')},q=function(d){var e=c.showMonthsShort?c.monthsShort:c.monthsFull;return"short_months"==d&&(e=c.monthsShort),c.selectMonths&&void 0==d?f.node("select",f.group({min:0,max:11,i:1,node:"option",item:function(a){return[e[a],0,"value="+a+(k.month==a?" selected":"")+(k.year==m.year&&a<m.month||k.year==n.year&&a>n.month?" disabled":"")]}}),c.klass.selectMonth+" browser-default",(a?"":"disabled")+" "+f.ariaAttr({controls:b.$node[0].id+"_table"})+' title="'+c.labelMonthSelect+'"'):"short_months"==d?null!=i?f.node("div",e[i.month]):f.node("div",e[k.month]):f.node("div",e[k.month],c.klass.month)},r=function(d){var e=k.year,g=c.selectYears===!0?5:~~(c.selectYears/2);if(g){var h=m.year,i=n.year,j=e-g,l=e+g;if(h>j&&(l+=h-j,j=h),i<l){var o=j-h,p=l-i;j-=o>p?p:o,l=i}if(c.selectYears&&void 0==d)return f.node("select",f.group({min:j,max:l,i:1,node:"option",item:function(a){return[a,0,"value="+a+(e==a?" selected":"")]}}),c.klass.selectYear+" browser-default",(a?"":"disabled")+" "+f.ariaAttr({controls:b.$node[0].id+"_table"})+' title="'+c.labelYearSelect+'"')}return"raw"==d?f.node("div",e):f.node("div",e,c.klass.year)};return createDayLabel=function(){return null!=i?f.node("div",i.date):f.node("div",h.date)},createWeekdayLabel=function(){var a;a=null!=i?i.day:h.day;var b=c.weekdaysFull[a];return b},f.node("div",f.node("div",createWeekdayLabel(),"picker__weekday-display")+f.node("div",q("short_months"),c.klass.month_display)+f.node("div",createDayLabel(),c.klass.day_display)+f.node("div",r("raw"),c.klass.year_display),c.klass.date_display)+f.node("div",f.node("div",(c.selectYears?q()+r():q()+r())+p()+p(1),c.klass.header)+f.node("table",o+f.node("tbody",f.group({min:0,max:e-1,i:1,node:"tr",item:function(a){var e=c.firstDay&&0===b.create([k.year,k.month,1]).day?-7:0;return[f.group({min:d*a-k.day+e+1,max:function(){return this.min+d-1},i:1,node:"td",item:function(a){a=b.create([k.year,k.month,a+(c.firstDay?1:0)]);var d=i&&i.pick==a.pick,e=j&&j.pick==a.pick,g=l&&b.disabled(a)||a.pick<m.pick||a.pick>n.pick,o=f.trigger(b.formats.toString,b,[c.format,a]);return[f.node("div",a.date,function(b){return b.push(k.month==a.month?c.klass.infocus:c.klass.outfocus),h.pick==a.pick&&b.push(c.klass.now),d&&b.push(c.klass.selected),e&&b.push(c.klass.highlighted),g&&b.push(c.klass.disabled),b.join(" ")}([c.klass.day]),"data-pick="+a.pick+" "+f.ariaAttr({role:"gridcell",label:o,selected:!(!d||b.$node.val()!==o)||null,activedescendant:!!e||null,disabled:!!g||null})),"",f.ariaAttr({role:"presentation"})]}})]}})),c.klass.table,'id="'+b.$node[0].id+'_table" '+f.ariaAttr({role:"grid",controls:b.$node[0].id,readonly:!0})),c.klass.calendar_container)+f.node("div",f.node("button",c.today,"btn-flat picker__today","type=button data-pick="+h.pick+(a&&!b.disabled(h)?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id}))+f.node("button",c.clear,"btn-flat picker__clear","type=button data-clear=1"+(a?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id}))+f.node("button",c.close,"btn-flat picker__close","type=button data-close=true "+(a?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id})),c.klass.footer)},c.defaults=function(a){return{labelMonthNext:"Next month",labelMonthPrev:"Previous month",labelMonthSelect:"Select a month",labelYearSelect:"Select a year",monthsFull:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdaysFull:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysLetter:["S","M","T","W","T","F","S"],today:"Today",clear:"Clear",close:"Close",format:"d mmmm, yyyy",klass:{table:a+"table",header:a+"header",date_display:a+"date-display",day_display:a+"day-display",month_display:a+"month-display",year_display:a+"year-display",calendar_container:a+"calendar-container",navPrev:a+"nav--prev",navNext:a+"nav--next",navDisabled:a+"nav--disabled",month:a+"month",year:a+"year",selectMonth:a+"select--month",selectYear:a+"select--year",weekdays:a+"weekday",day:a+"day",disabled:a+"day--disabled",selected:a+"day--selected",highlighted:a+"day--highlighted",now:a+"day--today",infocus:a+"day--infocus",outfocus:a+"day--outfocus",footer:a+"footer",buttonClear:a+"button--clear",buttonToday:a+"button--today",buttonClose:a+"button--close"}}}(a.klasses().picker+"__"),a.extend("pickadate",c)}),function(a){function b(){var b=+a(this).attr("data-length"),c=+a(this).val().length,d=c<=b;a(this).parent().find('span[class="character-counter"]').html(c+"/"+b),e(d,a(this))}function c(b){var c=b.parent().find('span[class="character-counter"]');c.length||(c=a("<span/>").addClass("character-counter").css("float","right").css("font-size","12px").css("height",1),b.parent().append(c))}function d(){a(this).parent().find('span[class="character-counter"]').html("")}function e(a,b){var c=b.hasClass("invalid");a&&c?b.removeClass("invalid"):a||c||(b.removeClass("valid"),b.addClass("invalid"))}a.fn.characterCounter=function(){return this.each(function(){var e=a(this),f=e.parent().find('span[class="character-counter"]');if(!f.length){var g=void 0!==e.attr("data-length");g&&(e.on("input",b),e.on("focus",b),e.on("blur",d),c(e))}})},a(document).ready(function(){a("input, textarea").characterCounter()})}(jQuery),function(a){var b={init:function(b){var c={duration:200,dist:-100,shift:0,padding:0,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null};return b=a.extend(c,b),this.each(function(){function c(){"undefined"!=typeof window.ontouchstart&&(J[0].addEventListener("touchstart",l),J[0].addEventListener("touchmove",m),J[0].addEventListener("touchend",n)),J[0].addEventListener("mousedown",l),J[0].addEventListener("mousemove",m),J[0].addEventListener("mouseup",n),J[0].addEventListener("mouseleave",n),J[0].addEventListener("click",j)}function d(a){return a.targetTouches&&a.targetTouches.length>=1?a.targetTouches[0].clientX:a.clientX}function e(a){return a.targetTouches&&a.targetTouches.length>=1?a.targetTouches[0].clientY:a.clientY}function f(a){return a>=v?a%v:a<0?f(v+a%v):a}function g(c){var d,e,g,h,i,j,k,l=s;if(r="number"==typeof c?c:r,s=Math.floor((r+u/2)/u),g=r-s*u,h=g<0?1:-1,i=-h*g*2/u,e=v>>1,b.fullWidth?k="translateX(0)":(k="translateX("+(J[0].clientWidth-p)/2+"px) ",k+="translateY("+(J[0].clientHeight-q)/2+"px)"),K){var m=s%v,n=I.find(".indicator-item.active");n.index()!==m&&(n.removeClass("active"),I.find(".indicator-item").eq(m).addClass("active"))}for((!b.noWrap||s>=0&&s<v)&&(j=o[f(s)],a(j).hasClass("active")||(J.find(".carousel-item").removeClass("active"),a(j).addClass("active")),j.style[C]=k+" translateX("+-g/2+"px) translateX("+h*b.shift*i*d+"px) translateZ("+b.dist*i+"px)",j.style.zIndex=0,b.fullWidth?tweenedOpacity=1:tweenedOpacity=1-.2*i,j.style.opacity=tweenedOpacity,j.style.display="block"),d=1;d<=e;++d)b.fullWidth?(zTranslation=b.dist,tweenedOpacity=d===e&&g<0?1-i:1):(zTranslation=b.dist*(2*d+i*h),tweenedOpacity=1-.2*(2*d+i*h)),(!b.noWrap||s+d<v)&&(j=o[f(s+d)],j.style[C]=k+" translateX("+(b.shift+(u*d-g)/2)+"px) translateZ("+zTranslation+"px)",j.style.zIndex=-d,j.style.opacity=tweenedOpacity,j.style.display="block"),b.fullWidth?(zTranslation=b.dist,tweenedOpacity=d===e&&g>0?1-i:1):(zTranslation=b.dist*(2*d-i*h),tweenedOpacity=1-.2*(2*d-i*h)),(!b.noWrap||s-d>=0)&&(j=o[f(s-d)],j.style[C]=k+" translateX("+(-b.shift+(-u*d-g)/2)+"px) translateZ("+zTranslation+"px)",j.style.zIndex=-d,j.style.opacity=tweenedOpacity,j.style.display="block");if((!b.noWrap||s>=0&&s<v)&&(j=o[f(s)],j.style[C]=k+" translateX("+-g/2+"px) translateX("+h*b.shift*i+"px) translateZ("+b.dist*i+"px)",j.style.zIndex=0,b.fullWidth?tweenedOpacity=1:tweenedOpacity=1-.2*i,j.style.opacity=tweenedOpacity,j.style.display="block"),l!==s&&"function"==typeof b.onCycleTo){var t=J.find(".carousel-item").eq(f(s));b.onCycleTo.call(this,t,G)}}function h(){var a,b,c,d;a=Date.now(),b=a-E,E=a,c=r-D,D=r,d=1e3*c/(1+b),B=.8*d+.2*B}function i(){var a,c;z&&(a=Date.now()-E,c=z*Math.exp(-a/b.duration),c>2||c<-2?(g(A-c),requestAnimationFrame(i)):g(A))}function j(c){if(G)return c.preventDefault(),c.stopPropagation(),!1;if(!b.fullWidth){var d=a(c.target).closest(".carousel-item").index(),e=s%v-d;0!==e&&(c.preventDefault(),c.stopPropagation()),k(d)}}function k(a){var c=s%v-a;b.noWrap||(c<0?Math.abs(c+v)<Math.abs(c)&&(c+=v):c>0&&Math.abs(c-v)<c&&(c-=v)),c<0?J.trigger("carouselNext",[Math.abs(c)]):c>0&&J.trigger("carouselPrev",[c])}function l(a){t=!0,G=!1,H=!1,w=d(a),x=e(a),B=z=0,D=r,E=Date.now(),clearInterval(F),F=setInterval(h,100)}function m(a){var b,c,f;if(t)if(b=d(a),y=e(a),c=w-b,f=Math.abs(x-y),f<30&&!H)(c>2||c<-2)&&(G=!0,w=b,g(r+c));else{if(G)return a.preventDefault(),a.stopPropagation(),!1;H=!0}if(G)return a.preventDefault(),a.stopPropagation(),!1}function n(a){if(t)return t=!1,clearInterval(F),A=r,(B>10||B<-10)&&(z=.9*B,A=r+z),A=Math.round(A/u)*u,b.noWrap&&(A>=u*(v-1)?A=u*(v-1):A<0&&(A=0)),z=A-r,E=Date.now(),requestAnimationFrame(i),G&&(a.preventDefault(),a.stopPropagation()),!1}var o,p,q,r,s,t,u,v,w,x,z,A,B,C,D,E,F,G,H,I=a('<ul class="indicators"></ul>'),J=a(this),K=J.attr("data-indicators")||b.indicators;if(J.hasClass("initialized"))return a(this).trigger("carouselNext",[1e-6]),!0;if(b.fullWidth){b.dist=0;var L=J.find(".carousel-item img").first();L.length?imageHeight=L.on("load",function(){J.css("height",a(this).height())}):(imageHeight=J.find(".carousel-item").first().height(),J.css("height",imageHeight)),K&&J.find(".carousel-fixed-item").addClass("with-indicators")}J.addClass("initialized"),t=!1,r=A=0,o=[],p=J.find(".carousel-item").first().innerWidth(),q=J.find(".carousel-item").first().innerHeight(),u=2*p+b.padding,J.find(".carousel-item").each(function(b){if(o.push(a(this)[0]),K){var c=a('<li class="indicator-item"></li>');0===b&&c.addClass("active"),c.click(function(b){b.stopPropagation();var c=a(this).index();k(c)}),I.append(c)}}),K&&J.append(I),v=o.length,C="transform",["webkit","Moz","O","ms"].every(function(a){var b=a+"Transform";return"undefined"==typeof document.body.style[b]||(C=b,!1)}),a(window).on("resize.carousel",function(){b.fullWidth?(p=J.find(".carousel-item").first().innerWidth(),q=J.find(".carousel-item").first().innerHeight(),u=2*p+b.padding,r=2*s*p,A=r):g()}),c(),g(r),a(this).on("carouselNext",function(a,b){void 0===b&&(b=1),A=u*Math.round(r/u)+u*b,r!==A&&(z=A-r,E=Date.now(),requestAnimationFrame(i))}),a(this).on("carouselPrev",function(a,b){void 0===b&&(b=1),A=u*Math.round(r/u)-u*b,r!==A&&(z=A-r,E=Date.now(),requestAnimationFrame(i))}),a(this).on("carouselSet",function(a,b){void 0===b&&(b=0),k(b)})})},next:function(b){a(this).trigger("carouselNext",[b])},prev:function(b){a(this).trigger("carouselPrev",[b])},set:function(b){a(this).trigger("carouselSet",[b])}};a.fn.carousel=function(c){return b[c]?b[c].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof c&&c?void a.error("Method "+c+" does not exist on jQuery.carousel"):b.init.apply(this,arguments)}}(jQuery);
\ No newline at end of file
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/typeahead.js b/utils/test/vnfcatalogue/VNF_Catalogue/public/3rd_party/typeahead.js
new file mode 100644 (file)
index 0000000..11235e7
--- /dev/null
@@ -0,0 +1,7 @@
+/*!
+ * typeahead.js 0.10.4
+ * https://github.com/twitter/typeahead.js
+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+!function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}}}(),c="0.10.4",d=function(){"use strict";function a(a){return a=b.toStr(a),a?a.split(/\s+/):[]}function c(a){return a=b.toStr(a),a?a.split(/\W+/):[]}function d(a){return function(){var c=[].slice.call(arguments,0);return function(d){var e=[];return b.each(c,function(c){e=e.concat(a(b.toStr(d[c])))}),e}}}return{nonword:c,whitespace:a,obj:{nonword:d(c),whitespace:d(a)}}}(),e=function(){"use strict";function c(c){this.maxSize=b.isNumber(c)?c:100,this.reset(),this.maxSize<=0&&(this.set=this.get=a.noop)}function d(){this.head=this.tail=null}function e(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(c.prototype,{set:function(a,b){var c,d=this.list.tail;this.size>=this.maxSize&&(this.list.remove(d),delete this.hash[d.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new e(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0},reset:function(){this.size=0,this.hash={},this.list=new d}}),b.mixin(d.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),c}(),f=function(){"use strict";function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+b.escapeRegExChars(this.prefix))}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),g=function(){"use strict";function c(b){b=b||{},this.cancelled=!1,this.lastUrl=null,this._send=b.transport?d(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get,this._cache=b.cache===!1?new e(0):i}function d(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new e(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i.reset()},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(null,b),k._cache.set(a,b)}function e(){c&&c(!0)}function i(){f--,delete g[a],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var j,k=this;this.cancelled||a!==this.lastUrl||((j=g[a])?j.done(d).fail(e):h>f?(f++,g[a]=this._send(a,b).done(d).fail(e).always(i)):this.onDeckRequestArgs=[].slice.call(arguments,0))},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),this.cancelled=!1,this.lastUrl=a,(e=this._cache.get(a))?b.defer(function(){d&&d(null,e)}):this._get(a,c,d),!!e},cancel:function(){this.cancelled=!0}}),c}(),h=function(){"use strict";function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.reset()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0,e=a.length;e>d;d++)b[a[d]]||(b[a[d]]=!0,c.push(a[d]));return c}function g(a,b){function c(a,b){return a-b}var d=0,e=0,f=[];a=a.sort(c),b=b.sort(c);for(var g=a.length,h=b.length;g>d&&h>e;)a[d]<b[e]?d++:a[d]>b[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},reset:function(){this.datums=[],this.trie=e()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),i=function(){"use strict";function d(a){return a.local||null}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,cache:!0,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}();!function(c){"use strict";function e(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=j(b.sorter),this.dupDetector=b.dupDetector||k,this.local=i.local(b),this.prefetch=i.prefetch(b),this.remote=i.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new h({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new f(this.cacheKey):null}function j(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function k(){return!1}var l,m;return l=c.Bloodhound,m={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.Bloodhound=e,e.noConflict=function(){return c.Bloodhound=l,e},e.tokenizers=d,b.mixin(e.prototype,{_loadPrefetch:function(b){function c(a){f.clear(),f.add(b.filter?b.filter(a):a),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a,c){b(a?[]:f.remote.filter?f.remote.filter(c):c)}var d,e,f=this;if(this.transport)return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_cancelLastRemoteRequest:function(){this.transport&&this.transport.cancel()},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(m.data,a,c),this.storage.set(m.protocol,location.protocol,c),this.storage.set(m.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(m.data),c.protocol=this.storage.get(m.protocol),c.thumbprint=this.storage.get(m.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},_initialize:function(){function c(){e.add(b.isFunction(f)?f():f)}var d,e=this,f=this.local;return d=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),f&&d.done(c),this.transport=this.remote?new g(this.remote):null,this.initPromise=d.promise()},initialize:function(a){return!this.initPromise||a?this._initialize():this.initPromise},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=f.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return e.dupDetector(a,b)}),!c&&d.push(a),d.length<e.limit}),c&&c(e.sorter(d))}var e=this,f=[],g=!1;f=this.index.get(a),f=this.sorter(f).slice(0,this.limit),f.length<this.limit?g=this._getFromRemote(a,d):this._cancelLastRemoteRequest(),g||(f.length>0||!this.transport)&&c&&c(f)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&g.resetCache()},ttAdapter:function(){return b.bind(this.get,this)}}),e}(this);var j=function(){return{wrapper:'<span class="twitter-typeahead"></span>',dropdown:'<span class="tt-dropdown-menu"></span>',dataset:'<div class="tt-dataset-%CLASS%"></div>',suggestions:'<span class="tt-suggestions"></span>',suggestion:'<div class="tt-suggestion"></div>'}}(),k=function(){"use strict";var a={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none",opacity:"1"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};return b.isMsie()&&b.mixin(a.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),b.isMsie()&&b.isMsie()<=7&&b.mixin(a.input,{marginTop:"-1px"}),a}(),l=function(){"use strict";function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return b.mixin(c.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),c}(),m=function(){"use strict";function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0,f=a.length;!d&&f>e;e+=1)d=a[e].apply(b,c)===!1;return!d}return d}function g(){var a;return a=window.setImmediate?function(a){setImmediate(function(){a()})}:function(a){setTimeout(function(){a()},0)}}function h(a,b){return a.bind?a.bind(b):function(){a.apply(b,[].slice.call(arguments,0))}}var i=/\s+/,j=g();return{onSync:c,onAsync:b,off:d,trigger:e}}(),n=function(a){"use strict";function c(a,c,d){for(var e,f=[],g=0,h=a.length;h>g;g++)f.push(b.escapeRegExChars(a[g]));return e=d?"\\b("+f.join("|")+")\\b":"("+f.join("|")+")",c?new RegExp(e):new RegExp(e,"i")}var d={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(e){function f(b){var c,d,f;return(c=h.exec(b.data))&&(f=a.createElement(e.tagName),e.className&&(f.className=e.className),d=b.splitText(c.index),d.splitText(c[0].length),f.appendChild(d.cloneNode(!0)),b.parentNode.replaceChild(f,d)),!!c}function g(a,b){for(var c,d=3,e=0;e<a.childNodes.length;e++)c=a.childNodes[e],c.nodeType===d?e+=b(c)?1:0:g(c,b)}var h;e=b.mixin({},d,e),e.node&&e.pattern&&(e.pattern=b.isArray(e.pattern)?e.pattern:[e.pattern],h=c(e.pattern,e.caseSensitive,e.wordsOnly),g(e.node,f))}}(window.document),o=function(){"use strict";function c(c){var e,f,h,i,j=this;c=c||{},c.input||a.error("input is missing"),e=b.bind(this._onBlur,this),f=b.bind(this._onFocus,this),h=b.bind(this._onKeydown,this),i=b.bind(this._onInput,this),this.$hint=a(c.hint),this.$input=a(c.input).on("blur.tt",e).on("focus.tt",f).on("keydown.tt",h),0===this.$hint.length&&(this.setHint=this.getHint=this.clearHint=this.clearHintIfInvalid=b.noop),b.isMsie()?this.$input.on("keydown.tt keypress.tt cut.tt paste.tt",function(a){g[a.which||a.keyCode]||b.defer(b.bind(j._onInput,j,a))}):this.$input.on("input.tt",i),this.query=this.$input.val(),this.$overflowHelper=d(this.$input)}function d(b){return a('<pre aria-hidden="true"></pre>').css({position:"absolute",visibility:"hidden",whiteSpace:"pre",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function e(a,b){return c.normalizeQuery(a)===c.normalizeQuery(b)}function f(a){return a.altKey||a.ctrlKey||a.metaKey||a.shiftKey}var g;return g={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},c.normalizeQuery=function(a){return(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," ")},b.mixin(c.prototype,m,{_onBlur:function(){this.resetInputValue(),this.trigger("blurred")},_onFocus:function(){this.trigger("focused")},_onKeydown:function(a){var b=g[a.which||a.keyCode];this._managePreventDefault(b,a),b&&this._shouldTrigger(b,a)&&this.trigger(b+"Keyed",a)},_onInput:function(){this._checkInputValue()},_managePreventDefault:function(a,b){var c,d,e;switch(a){case"tab":d=this.getHint(),e=this.getInputValue(),c=d&&d!==e&&!f(b);break;case"up":case"down":c=!f(b);break;default:c=!1}c&&b.preventDefault()},_shouldTrigger:function(a,b){var c;switch(a){case"tab":c=!f(b);break;default:c=!0}return c},_checkInputValue:function(){var a,b,c;a=this.getInputValue(),b=e(a,this.query),c=b?this.query.length!==a.length:!1,this.query=a,b?c&&this.trigger("whitespaceChanged",this.query):this.trigger("queryChanged",this.query)},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),b?this.clearHint():this._checkInputValue()},resetInputValue:function(){this.setInputValue(this.query,!0)},getHint:function(){return this.$hint.val()},setHint:function(a){this.$hint.val(a)},clearHint:function(){this.setHint("")},clearHintIfInvalid:function(){var a,b,c,d;a=this.getInputValue(),b=this.getHint(),c=a!==b&&0===b.indexOf(a),d=""!==a&&c&&!this.hasOverflow(),!d&&this.clearHint()},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},hasOverflow:function(){var a=this.$input.width()-2;return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>=a},isCursorAtEnd:function(){var a,c,d;return a=this.$input.val().length,c=this.$input[0].selectionStart,b.isNumber(c)?c===a:document.selection?(d=document.selection.createRange(),d.moveStart("character",-a),a===d.text.length):!0},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null}}),c}(),p=function(){"use strict";function c(c){c=c||{},c.templates=c.templates||{},c.source||a.error("missing source"),c.name&&!f(c.name)&&a.error("invalid dataset name: "+c.name),this.query=null,this.highlight=!!c.highlight,this.name=c.name||b.getUniqueId(),this.source=c.source,this.displayFn=d(c.display||c.displayKey),this.templates=e(c.templates,this.displayFn),this.$el=a(j.dataset.replace("%CLASS%",this.name))}function d(a){function c(b){return b[a]}return a=a||"value",b.isFunction(a)?a:c}function e(a,c){function d(a){return"<p>"+c(a)+"</p>"}return{empty:a.empty&&b.templatify(a.empty),header:a.header&&b.templatify(a.header),footer:a.footer&&b.templatify(a.footer),suggestion:a.suggestion||d}}function f(a){return/^[_a-zA-Z0-9-]+$/.test(a)}var g="ttDataset",h="ttValue",i="ttDatum";return c.extractDatasetName=function(b){return a(b).data(g)},c.extractValue=function(b){return a(b).data(h)},c.extractDatum=function(b){return a(b).data(i)},b.mixin(c.prototype,m,{_render:function(c,d){function e(){return p.templates.empty({query:c,isEmpty:!0})}function f(){function e(b){var c;return c=a(j.suggestion).append(p.templates.suggestion(b)).data(g,p.name).data(h,p.displayFn(b)).data(i,b),c.children().each(function(){a(this).css(k.suggestionChild)}),c}var f,l;return f=a(j.suggestions).css(k.suggestions),l=b.map(d,e),f.append.apply(f,l),p.highlight&&n({className:"tt-highlight",node:f[0],pattern:c}),f}function l(){return p.templates.header({query:c,isEmpty:!o})}function m(){return p.templates.footer({query:c,isEmpty:!o})}if(this.$el){var o,p=this;this.$el.empty(),o=d&&d.length,!o&&this.templates.empty?this.$el.html(e()).prepend(p.templates.header?l():null).append(p.templates.footer?m():null):o&&this.$el.html(f()).prepend(p.templates.header?l():null).append(p.templates.footer?m():null),this.trigger("rendered")}},getRoot:function(){return this.$el},update:function(a){function b(b){c.canceled||a!==c.query||c._render(a,b)}var c=this;this.query=a,this.canceled=!1,this.source(a,b)},cancel:function(){this.canceled=!0},clear:function(){this.cancel(),this.$el.empty(),this.trigger("rendered")},isEmpty:function(){return this.$el.is(":empty")},destroy:function(){this.$el=null}}),c}(),q=function(){"use strict";function c(c){var e,f,g,h=this;c=c||{},c.menu||a.error("menu is required"),this.isOpen=!1,this.isEmpty=!0,this.datasets=b.map(c.datasets,d),e=b.bind(this._onSuggestionClick,this),f=b.bind(this._onSuggestionMouseEnter,this),g=b.bind(this._onSuggestionMouseLeave,this),this.$menu=a(c.menu).on("click.tt",".tt-suggestion",e).on("mouseenter.tt",".tt-suggestion",f).on("mouseleave.tt",".tt-suggestion",g),b.each(this.datasets,function(a){h.$menu.append(a.getRoot()),a.onSync("rendered",h._onRendered,h)})}function d(a){return new p(a)}return b.mixin(c.prototype,m,{_onSuggestionClick:function(b){this.trigger("suggestionClicked",a(b.currentTarget))},_onSuggestionMouseEnter:function(b){this._removeCursor(),this._setCursor(a(b.currentTarget),!0)},_onSuggestionMouseLeave:function(){this._removeCursor()},_onRendered:function(){function a(a){return a.isEmpty()}this.isEmpty=b.every(this.datasets,a),this.isEmpty?this._hide():this.isOpen&&this._show(),this.trigger("datasetRendered")},_hide:function(){this.$menu.hide()},_show:function(){this.$menu.css("display","block")},_getSuggestions:function(){return this.$menu.find(".tt-suggestion")},_getCursor:function(){return this.$menu.find(".tt-cursor").first()},_setCursor:function(a,b){a.first().addClass("tt-cursor"),!b&&this.trigger("cursorMoved")},_removeCursor:function(){this._getCursor().removeClass("tt-cursor")},_moveCursor:function(a){var b,c,d,e;if(this.isOpen){if(c=this._getCursor(),b=this._getSuggestions(),this._removeCursor(),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return void this.trigger("cursorRemoved");-1>d&&(d=b.length-1),this._setCursor(e=b.eq(d)),this._ensureVisible(e)}},_ensureVisible:function(a){var b,c,d,e;b=a.position().top,c=b+a.outerHeight(!0),d=this.$menu.scrollTop(),e=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),0>b?this.$menu.scrollTop(d+b):c>e&&this.$menu.scrollTop(d+(c-e))},close:function(){this.isOpen&&(this.isOpen=!1,this._removeCursor(),this._hide(),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){this.$menu.css("ltr"===a?k.ltr:k.rtl)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getDatumForSuggestion:function(a){var b=null;return a.length&&(b={raw:p.extractDatum(a),value:p.extractValue(a),datasetName:p.extractDatasetName(a)}),b},getDatumForCursor:function(){return this.getDatumForSuggestion(this._getCursor().first())},getDatumForTopSuggestion:function(){return this.getDatumForSuggestion(this._getSuggestions().first())},update:function(a){function c(b){b.update(a)}b.each(this.datasets,c)},empty:function(){function a(a){a.clear()}b.each(this.datasets,a),this.isEmpty=!0},isVisible:function(){return this.isOpen&&!this.isEmpty},destroy:function(){function a(a){a.destroy()}this.$menu.off(".tt"),this.$menu=null,b.each(this.datasets,a)}}),c}(),r=function(){"use strict";function c(c){var e,f,g;c=c||{},c.input||a.error("missing input"),this.isActivated=!1,this.autoselect=!!c.autoselect,this.minLength=b.isNumber(c.minLength)?c.minLength:1,this.$node=d(c.input,c.withHint),e=this.$node.find(".tt-dropdown-menu"),f=this.$node.find(".tt-input"),g=this.$node.find(".tt-hint"),f.on("blur.tt",function(a){var c,d,g;c=document.activeElement,d=e.is(c),g=e.has(c).length>0,b.isMsie()&&(d||g)&&(a.preventDefault(),a.stopImmediatePropagation(),b.defer(function(){f.focus()}))}),e.on("mousedown.tt",function(a){a.preventDefault()}),this.eventBus=c.eventBus||new l({el:f}),this.dropdown=new q({menu:e,datasets:c.datasets}).onSync("suggestionClicked",this._onSuggestionClicked,this).onSync("cursorMoved",this._onCursorMoved,this).onSync("cursorRemoved",this._onCursorRemoved,this).onSync("opened",this._onOpened,this).onSync("closed",this._onClosed,this).onAsync("datasetRendered",this._onDatasetRendered,this),this.input=new o({input:f,hint:g}).onSync("focused",this._onFocused,this).onSync("blurred",this._onBlurred,this).onSync("enterKeyed",this._onEnterKeyed,this).onSync("tabKeyed",this._onTabKeyed,this).onSync("escKeyed",this._onEscKeyed,this).onSync("upKeyed",this._onUpKeyed,this).onSync("downKeyed",this._onDownKeyed,this).onSync("leftKeyed",this._onLeftKeyed,this).onSync("rightKeyed",this._onRightKeyed,this).onSync("queryChanged",this._onQueryChanged,this).onSync("whitespaceChanged",this._onWhitespaceChanged,this),this._setLanguageDirection()}function d(b,c){var d,f,h,i;d=a(b),f=a(j.wrapper).css(k.wrapper),h=a(j.dropdown).css(k.dropdown),i=d.clone().css(k.hint).css(e(d)),i.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder required").prop("readonly",!0).attr({autocomplete:"off",spellcheck:"false",tabindex:-1}),d.data(g,{dir:d.attr("dir"),autocomplete:d.attr("autocomplete"),spellcheck:d.attr("spellcheck"),style:d.attr("style")}),d.addClass("tt-input").attr({autocomplete:"off",spellcheck:!1}).css(c?k.input:k.inputWithNoHint);try{!d.attr("dir")&&d.attr("dir","auto")}catch(l){}return d.wrap(f).parent().prepend(c?i:null).append(h)}function e(a){return{backgroundAttachment:a.css("background-attachment"),backgroundClip:a.css("background-clip"),backgroundColor:a.css("background-color"),backgroundImage:a.css("background-image"),backgroundOrigin:a.css("background-origin"),backgroundPosition:a.css("background-position"),backgroundRepeat:a.css("background-repeat"),backgroundSize:a.css("background-size")}}function f(a){var c=a.find(".tt-input");b.each(c.data(g),function(a,d){b.isUndefined(a)?c.removeAttr(d):c.attr(d,a)}),c.detach().removeData(g).removeClass("tt-input").insertAfter(a),a.remove()}var g="ttAttrs";return b.mixin(c.prototype,{_onSuggestionClicked:function(a,b){var c;(c=this.dropdown.getDatumForSuggestion(b))&&this._select(c)},_onCursorMoved:function(){var a=this.dropdown.getDatumForCursor();this.input.setInputValue(a.value,!0),this.eventBus.trigger("cursorchanged",a.raw,a.datasetName)},_onCursorRemoved:function(){this.input.resetInputValue(),this._updateHint()},_onDatasetRendered:function(){this._updateHint()},_onOpened:function(){this._updateHint(),this.eventBus.trigger("opened")},_onClosed:function(){this.input.clearHint(),this.eventBus.trigger("closed")},_onFocused:function(){this.isActivated=!0,this.dropdown.open()},_onBlurred:function(){this.isActivated=!1,this.dropdown.empty(),this.dropdown.close()},_onEnterKeyed:function(a,b){var c,d;c=this.dropdown.getDatumForCursor(),d=this.dropdown.getDatumForTopSuggestion(),c?(this._select(c),b.preventDefault()):this.autoselect&&d&&(this._select(d),b.preventDefault())},_onTabKeyed:function(a,b){var c;(c=this.dropdown.getDatumForCursor())?(this._select(c),b.preventDefault()):this._autocomplete(!0)},_onEscKeyed:function(){this.dropdown.close(),this.input.resetInputValue()},_onUpKeyed:function(){var a=this.input.getQuery();this.dropdown.isEmpty&&a.length>=this.minLength?this.dropdown.update(a):this.dropdown.moveCursorUp(),this.dropdown.open()},_onDownKeyed:function(){var a=this.input.getQuery();this.dropdown.isEmpty&&a.length>=this.minLength?this.dropdown.update(a):this.dropdown.moveCursorDown(),this.dropdown.open()},_onLeftKeyed:function(){"rtl"===this.dir&&this._autocomplete()},_onRightKeyed:function(){"ltr"===this.dir&&this._autocomplete()},_onQueryChanged:function(a,b){this.input.clearHintIfInvalid(),b.length>=this.minLength?this.dropdown.update(b):this.dropdown.empty(),this.dropdown.open(),this._setLanguageDirection()},_onWhitespaceChanged:function(){this._updateHint(),this.dropdown.open()},_setLanguageDirection:function(){var a;this.dir!==(a=this.input.getLanguageDirection())&&(this.dir=a,this.$node.css("direction",a),this.dropdown.setLanguageDirection(a))},_updateHint:function(){var a,c,d,e,f,g;a=this.dropdown.getDatumForTopSuggestion(),a&&this.dropdown.isVisible()&&!this.input.hasOverflow()?(c=this.input.getInputValue(),d=o.normalizeQuery(c),e=b.escapeRegExChars(d),f=new RegExp("^(?:"+e+")(.+$)","i"),g=f.exec(a.value),g?this.input.setHint(c+g[1]):this.input.clearHint()):this.input.clearHint()},_autocomplete:function(a){var b,c,d,e;b=this.input.getHint(),c=this.input.getQuery(),d=a||this.input.isCursorAtEnd(),b&&c!==b&&d&&(e=this.dropdown.getDatumForTopSuggestion(),e&&this.input.setInputValue(e.value),this.eventBus.trigger("autocompleted",e.raw,e.datasetName))},_select:function(a){this.input.setQuery(a.value),this.input.setInputValue(a.value,!0),this._setLanguageDirection(),this.eventBus.trigger("selected",a.raw,a.datasetName),this.dropdown.close(),b.defer(b.bind(this.dropdown.empty,this.dropdown))},open:function(){this.dropdown.open()},close:function(){this.dropdown.close()},setVal:function(a){a=b.toStr(a),this.isActivated?this.input.setInputValue(a):(this.input.setQuery(a),this.input.setInputValue(a,!0)),this._setLanguageDirection()},getVal:function(){return this.input.getQuery()},destroy:function(){this.input.destroy(),this.dropdown.destroy(),f(this.$node),this.$node=null}}),c}();!function(){"use strict";var c,d,e;c=a.fn.typeahead,d="ttTypeahead",e={initialize:function(c,e){function f(){var f,g,h=a(this);b.each(e,function(a){a.highlight=!!c.highlight}),g=new r({input:h,eventBus:f=new l({el:h}),withHint:b.isUndefined(c.hint)?!0:!!c.hint,minLength:c.minLength,autoselect:c.autoselect,datasets:e}),h.data(d,g)}return e=b.isArray(e)?e:[].slice.call(arguments,1),c=c||{},this.each(f)},open:function(){function b(){var b,c=a(this);(b=c.data(d))&&b.open()}return this.each(b)},close:function(){function b(){var b,c=a(this);(b=c.data(d))&&b.close()}return this.each(b)},val:function(b){function c(){var c,e=a(this);(c=e.data(d))&&c.setVal(b)}function e(a){var b,c;return(b=a.data(d))&&(c=b.getVal()),c}return arguments.length?this.each(c):e(this.first())},destroy:function(){function b(){var b,c=a(this);(b=c.data(d))&&(b.destroy(),c.removeData(d))}return this.each(b)}},a.fn.typeahead=function(b){var c;return e[b]&&"initialize"!==b?(c=this.filter(function(){return!!a(this).data(d)}),e[b].apply(c,[].slice.call(arguments,1))):e.initialize.apply(this,arguments)},a.fn.typeahead.noConflict=function(){return a.fn.typeahead=c,this}}()}(window.jQuery);
\ No newline at end of file
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/images/3rd_party/commits.png b/utils/test/vnfcatalogue/VNF_Catalogue/public/images/3rd_party/commits.png
new file mode 100644 (file)
index 0000000..1247621
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/images/3rd_party/commits.png differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/images/logo.png b/utils/test/vnfcatalogue/VNF_Catalogue/public/images/logo.png
new file mode 100644 (file)
index 0000000..fe18194
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/images/logo.png differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/global.js b/utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/global.js
new file mode 100644 (file)
index 0000000..f610456
--- /dev/null
@@ -0,0 +1,87 @@
+/*******************************************************************************\r
+ * Copyright (c) 2017 Kumar Rishabh and others.\r
+ *\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Apache License, Version 2.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *******************************************************************************/\r
+\r
+$(document).ready( function() {\r
+    $('select').material_select();\r
+    $('.modal').modal();\r
+    $(".button-collapse").sideNav();\r
+    $('.carousel').carousel();\r
+\r
+    $('#Search').click(function() {\r
+        var tags = $('#Tags').val().toLowerCase().split(/[ ,]+/);\r
+        window.location.href = '/search_projects?tags=' + tags;\r
+        return false;\r
+    });\r
+\r
+    $('#SearchSpan').click(function(){\r
+        var tags = $('#Tags').val().toLowerCase().split(/[ ,]+/);\r
+        window.location.href = '/search_projects?tags=' + tags;\r
+        return false;\r
+    });\r
+\r
+    $('div.form-group-custom i.material-icons').click(function(e){\r
+        var tags = $('#Tags').val().toLowerCase().split(/[ ,]+/);\r
+        window.location.href = '/search_projects?tags=' + tags;\r
+        return false;\r
+    });\r
+\r
+    $("#add_project_button").on('click',function(){\r
+        event.preventDefault();\r
+        var vnf_name = $("#vnf_name").val() ;\r
+\r
+        var formData = new FormData($('form#add_project_form')[0]);\r
+        var license = $('#license option:selected').val();\r
+        formData.append('license', license);\r
+        var opnfv_indicator = $('#opnfv_indicator option:selected').val();\r
+        formData.append('opnfv_indicator', opnfv_indicator);\r
+\r
+        $.ajax({\r
+            url: '/add_project',\r
+            type: 'post',\r
+            //dataType: 'json',\r
+            processData: false,  // tell jQuery not to process the data\r
+            contentType: false,  // tell jQuery not to set contentType\r
+            data: formData,\r
+            success: function(data) {\r
+                    $('#modal1').modal('close');\r
+                    $('form#add_project_form').trigger('reset');\r
+                    Materialize.toast('Successfully submitted the VNF!', 3000, 'rounded');\r
+            },\r
+            error: function (error) {\r
+                if(error['responseJSON']) {\r
+                    Materialize.toast(error['responseJSON']['error'], 3000, 'rounded');\r
+                } else if(error['responseText']) {\r
+                    var response_message = JSON.parse(error['responseText']);\r
+                    Materialize.toast(response_message['error'], 3000, 'rounded');\r
+                }\r
+                //$('#modal1').modal('open');\r
+            }\r
+        });\r
+    });\r
+    $("#add_tag_button").on('click',function(){\r
+        event.preventDefault();\r
+        var tag_name = $("#tag_name").val() ;\r
+\r
+        $.ajax({\r
+            url: '/add_tag',\r
+            type: 'post',\r
+            dataType: 'json',\r
+            data: $('form#add_tag_form').serialize(),\r
+            success: function(data) {\r
+                    $('#modal2').modal('close');\r
+                    $('form#add_tag_form').trigger('reset');\r
+                    Materialize.toast('Successfully submitted the TAG!', 3000, 'rounded');\r
+            },\r
+            error: function (error) {\r
+                Materialize.toast(error['responseJSON']['error'], 3000, 'rounded');\r
+            }\r
+        });\r
+    });\r
+\r
+});\r
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/mode_edit.js b/utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/mode_edit.js
new file mode 100644 (file)
index 0000000..2047a92
--- /dev/null
@@ -0,0 +1,82 @@
+/*******************************************************************************\r
+ * Copyright (c) 2017 Kumar Rishabh and others.\r
+ *\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Apache License, Version 2.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *******************************************************************************/\r
+\r
+$(document).ready( function() {\r
+\r
+    //getVnfs : get 5 main VNFs using typeahead\r
+    var getVnfs = new Bloodhound({\r
+        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('vnf_name'),\r
+        queryTokenizer: Bloodhound.tokenizers.obj.whitespace('vnf_name'),\r
+        remote: {\r
+            url: '/search_vnf?key=%QUERY',\r
+            wildcard: '%QUERY'\r
+        },\r
+        limit: 5\r
+    });\r
+\r
+    getVnfs.initialize();\r
+    $('#scrollable-dropdown-menu #vnf_name.typeahead').typeahead(\r
+    {\r
+        hint: true,\r
+        highlight: true,\r
+        minLength: 1\r
+    },\r
+    {\r
+        name: 'vnf_name',\r
+        display: 'vnf_name',\r
+        limit: 5,\r
+        source: getVnfs.ttAdapter()\r
+    });\r
+\r
+    //getTags : get 5 main tags using typeahead\r
+    var getTags = new Bloodhound({\r
+        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('tag_name'),\r
+        queryTokenizer: Bloodhound.tokenizers.obj.whitespace('tag_name'),\r
+        remote: {\r
+            url: '/search_tag?key=%QUERY',\r
+            wildcard: '%QUERY'\r
+        },\r
+        limit: 5\r
+    });\r
+\r
+    getTags.initialize();\r
+    $('#scrollable-dropdown-menu #tag_name.typeahead').typeahead(\r
+    {\r
+        hint: true,\r
+        highlight: true,\r
+        minLength: 1\r
+    },\r
+    {\r
+        name: 'tag_name',\r
+        display: 'tag_name',\r
+        limit: 5,\r
+        source: getTags.ttAdapter()\r
+    });\r
+\r
+    $("#add_vnf_tag_association_button").on('click',function(){\r
+        event.preventDefault();\r
+        var vnf_name = $("#vnf_name").val() ;\r
+\r
+        $.ajax({\r
+            url: '/vnf_tag_association',\r
+            type: 'post',\r
+            dataType: 'json',\r
+            data: $('form#add_vnf_tag_association_form').serialize(),\r
+            success: function(data) {\r
+                    $('#modal3').modal('close');\r
+                    $('form#add_vnf_tag_association_form').trigger('reset');\r
+                    Materialize.toast('Successfully added the TAG to the VNF!', 3000, 'rounded');\r
+            },\r
+            error: function (error) {\r
+                Materialize.toast(error['responseJSON']['error'], 3000, 'rounded');\r
+            }\r
+        });\r
+    });\r
+\r
+});\r
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/search_results.js b/utils/test/vnfcatalogue/VNF_Catalogue/public/javascripts/search_results.js
new file mode 100644 (file)
index 0000000..26c28c9
--- /dev/null
@@ -0,0 +1,12 @@
+/*******************************************************************************\r
+ * Copyright (c) 2017 Kumar Rishabh and others.\r
+ *\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Apache License, Version 2.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *******************************************************************************/\r
+\r
+$(document).ready( function() {\r
+    var ob = JSON.parse(json);\r
+});\r
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/3rd_party/bootstrap.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/3rd_party/bootstrap.css
new file mode 100755 (executable)
index 0000000..6167622
--- /dev/null
@@ -0,0 +1,6757 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+body {
+  margin: 0;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+audio,
+canvas,
+progress,
+video {
+  display: inline-block;
+  vertical-align: baseline;
+}
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+[hidden],
+template {
+  display: none;
+}
+a {
+  background-color: transparent;
+}
+a:active,
+a:hover {
+  outline: 0;
+}
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+b,
+strong {
+  font-weight: bold;
+}
+dfn {
+  font-style: italic;
+}
+h1 {
+  margin: .67em 0;
+  font-size: 2em;
+}
+mark {
+  color: #000;
+  background: #ff0;
+}
+small {
+  font-size: 80%;
+}
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+sup {
+  top: -.5em;
+}
+sub {
+  bottom: -.25em;
+}
+img {
+  border: 0;
+}
+svg:not(:root) {
+  overflow: hidden;
+}
+figure {
+  margin: 1em 40px;
+}
+hr {
+  height: 0;
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+}
+pre {
+  overflow: auto;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+  margin: 0;
+  font: inherit;
+  color: inherit;
+}
+button {
+  overflow: visible;
+}
+button,
+select {
+  text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+input {
+  line-height: normal;
+}
+input[type="checkbox"],
+input[type="radio"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 0;
+}
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+fieldset {
+  padding: .35em .625em .75em;
+  margin: 0 2px;
+  border: 1px solid #c0c0c0;
+}
+legend {
+  padding: 0;
+  border: 0;
+}
+textarea {
+  overflow: auto;
+}
+optgroup {
+  font-weight: bold;
+}
+table {
+  border-spacing: 0;
+  border-collapse: collapse;
+}
+td,
+th {
+  padding: 0;
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print {
+  *,
+  *:before,
+  *:after {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    -webkit-box-shadow: none !important;
+            box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  a[href^="#"]:after,
+  a[href^="javascript:"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  .navbar {
+    display: none;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+@font-face {
+  font-family: 'Glyphicons Halflings';
+
+  src: url('../fonts/glyphicons-halflings-regular.eot');
+  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+.glyphicon {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-family: 'Glyphicons Halflings';
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.glyphicon-asterisk:before {
+  content: "\002a";
+}
+.glyphicon-plus:before {
+  content: "\002b";
+}
+.glyphicon-euro:before,
+.glyphicon-eur:before {
+  content: "\20ac";
+}
+.glyphicon-minus:before {
+  content: "\2212";
+}
+.glyphicon-cloud:before {
+  content: "\2601";
+}
+.glyphicon-envelope:before {
+  content: "\2709";
+}
+.glyphicon-pencil:before {
+  content: "\270f";
+}
+.glyphicon-glass:before {
+  content: "\e001";
+}
+.glyphicon-music:before {
+  content: "\e002";
+}
+.glyphicon-search:before {
+  content: "\e003";
+}
+.glyphicon-heart:before {
+  content: "\e005";
+}
+.glyphicon-star:before {
+  content: "\e006";
+}
+.glyphicon-star-empty:before {
+  content: "\e007";
+}
+.glyphicon-user:before {
+  content: "\e008";
+}
+.glyphicon-film:before {
+  content: "\e009";
+}
+.glyphicon-th-large:before {
+  content: "\e010";
+}
+.glyphicon-th:before {
+  content: "\e011";
+}
+.glyphicon-th-list:before {
+  content: "\e012";
+}
+.glyphicon-ok:before {
+  content: "\e013";
+}
+.glyphicon-remove:before {
+  content: "\e014";
+}
+.glyphicon-zoom-in:before {
+  content: "\e015";
+}
+.glyphicon-zoom-out:before {
+  content: "\e016";
+}
+.glyphicon-off:before {
+  content: "\e017";
+}
+.glyphicon-signal:before {
+  content: "\e018";
+}
+.glyphicon-cog:before {
+  content: "\e019";
+}
+.glyphicon-trash:before {
+  content: "\e020";
+}
+.glyphicon-home:before {
+  content: "\e021";
+}
+.glyphicon-file:before {
+  content: "\e022";
+}
+.glyphicon-time:before {
+  content: "\e023";
+}
+.glyphicon-road:before {
+  content: "\e024";
+}
+.glyphicon-download-alt:before {
+  content: "\e025";
+}
+.glyphicon-download:before {
+  content: "\e026";
+}
+.glyphicon-upload:before {
+  content: "\e027";
+}
+.glyphicon-inbox:before {
+  content: "\e028";
+}
+.glyphicon-play-circle:before {
+  content: "\e029";
+}
+.glyphicon-repeat:before {
+  content: "\e030";
+}
+.glyphicon-refresh:before {
+  content: "\e031";
+}
+.glyphicon-list-alt:before {
+  content: "\e032";
+}
+.glyphicon-lock:before {
+  content: "\e033";
+}
+.glyphicon-flag:before {
+  content: "\e034";
+}
+.glyphicon-headphones:before {
+  content: "\e035";
+}
+.glyphicon-volume-off:before {
+  content: "\e036";
+}
+.glyphicon-volume-down:before {
+  content: "\e037";
+}
+.glyphicon-volume-up:before {
+  content: "\e038";
+}
+.glyphicon-qrcode:before {
+  content: "\e039";
+}
+.glyphicon-barcode:before {
+  content: "\e040";
+}
+.glyphicon-tag:before {
+  content: "\e041";
+}
+.glyphicon-tags:before {
+  content: "\e042";
+}
+.glyphicon-book:before {
+  content: "\e043";
+}
+.glyphicon-bookmark:before {
+  content: "\e044";
+}
+.glyphicon-print:before {
+  content: "\e045";
+}
+.glyphicon-camera:before {
+  content: "\e046";
+}
+.glyphicon-font:before {
+  content: "\e047";
+}
+.glyphicon-bold:before {
+  content: "\e048";
+}
+.glyphicon-italic:before {
+  content: "\e049";
+}
+.glyphicon-text-height:before {
+  content: "\e050";
+}
+.glyphicon-text-width:before {
+  content: "\e051";
+}
+.glyphicon-align-left:before {
+  content: "\e052";
+}
+.glyphicon-align-center:before {
+  content: "\e053";
+}
+.glyphicon-align-right:before {
+  content: "\e054";
+}
+.glyphicon-align-justify:before {
+  content: "\e055";
+}
+.glyphicon-list:before {
+  content: "\e056";
+}
+.glyphicon-indent-left:before {
+  content: "\e057";
+}
+.glyphicon-indent-right:before {
+  content: "\e058";
+}
+.glyphicon-facetime-video:before {
+  content: "\e059";
+}
+.glyphicon-picture:before {
+  content: "\e060";
+}
+.glyphicon-map-marker:before {
+  content: "\e062";
+}
+.glyphicon-adjust:before {
+  content: "\e063";
+}
+.glyphicon-tint:before {
+  content: "\e064";
+}
+.glyphicon-edit:before {
+  content: "\e065";
+}
+.glyphicon-share:before {
+  content: "\e066";
+}
+.glyphicon-check:before {
+  content: "\e067";
+}
+.glyphicon-move:before {
+  content: "\e068";
+}
+.glyphicon-step-backward:before {
+  content: "\e069";
+}
+.glyphicon-fast-backward:before {
+  content: "\e070";
+}
+.glyphicon-backward:before {
+  content: "\e071";
+}
+.glyphicon-play:before {
+  content: "\e072";
+}
+.glyphicon-pause:before {
+  content: "\e073";
+}
+.glyphicon-stop:before {
+  content: "\e074";
+}
+.glyphicon-forward:before {
+  content: "\e075";
+}
+.glyphicon-fast-forward:before {
+  content: "\e076";
+}
+.glyphicon-step-forward:before {
+  content: "\e077";
+}
+.glyphicon-eject:before {
+  content: "\e078";
+}
+.glyphicon-chevron-left:before {
+  content: "\e079";
+}
+.glyphicon-chevron-right:before {
+  content: "\e080";
+}
+.glyphicon-plus-sign:before {
+  content: "\e081";
+}
+.glyphicon-minus-sign:before {
+  content: "\e082";
+}
+.glyphicon-remove-sign:before {
+  content: "\e083";
+}
+.glyphicon-ok-sign:before {
+  content: "\e084";
+}
+.glyphicon-question-sign:before {
+  content: "\e085";
+}
+.glyphicon-info-sign:before {
+  content: "\e086";
+}
+.glyphicon-screenshot:before {
+  content: "\e087";
+}
+.glyphicon-remove-circle:before {
+  content: "\e088";
+}
+.glyphicon-ok-circle:before {
+  content: "\e089";
+}
+.glyphicon-ban-circle:before {
+  content: "\e090";
+}
+.glyphicon-arrow-left:before {
+  content: "\e091";
+}
+.glyphicon-arrow-right:before {
+  content: "\e092";
+}
+.glyphicon-arrow-up:before {
+  content: "\e093";
+}
+.glyphicon-arrow-down:before {
+  content: "\e094";
+}
+.glyphicon-share-alt:before {
+  content: "\e095";
+}
+.glyphicon-resize-full:before {
+  content: "\e096";
+}
+.glyphicon-resize-small:before {
+  content: "\e097";
+}
+.glyphicon-exclamation-sign:before {
+  content: "\e101";
+}
+.glyphicon-gift:before {
+  content: "\e102";
+}
+.glyphicon-leaf:before {
+  content: "\e103";
+}
+.glyphicon-fire:before {
+  content: "\e104";
+}
+.glyphicon-eye-open:before {
+  content: "\e105";
+}
+.glyphicon-eye-close:before {
+  content: "\e106";
+}
+.glyphicon-warning-sign:before {
+  content: "\e107";
+}
+.glyphicon-plane:before {
+  content: "\e108";
+}
+.glyphicon-calendar:before {
+  content: "\e109";
+}
+.glyphicon-random:before {
+  content: "\e110";
+}
+.glyphicon-comment:before {
+  content: "\e111";
+}
+.glyphicon-magnet:before {
+  content: "\e112";
+}
+.glyphicon-chevron-up:before {
+  content: "\e113";
+}
+.glyphicon-chevron-down:before {
+  content: "\e114";
+}
+.glyphicon-retweet:before {
+  content: "\e115";
+}
+.glyphicon-shopping-cart:before {
+  content: "\e116";
+}
+.glyphicon-folder-close:before {
+  content: "\e117";
+}
+.glyphicon-folder-open:before {
+  content: "\e118";
+}
+.glyphicon-resize-vertical:before {
+  content: "\e119";
+}
+.glyphicon-resize-horizontal:before {
+  content: "\e120";
+}
+.glyphicon-hdd:before {
+  content: "\e121";
+}
+.glyphicon-bullhorn:before {
+  content: "\e122";
+}
+.glyphicon-bell:before {
+  content: "\e123";
+}
+.glyphicon-certificate:before {
+  content: "\e124";
+}
+.glyphicon-thumbs-up:before {
+  content: "\e125";
+}
+.glyphicon-thumbs-down:before {
+  content: "\e126";
+}
+.glyphicon-hand-right:before {
+  content: "\e127";
+}
+.glyphicon-hand-left:before {
+  content: "\e128";
+}
+.glyphicon-hand-up:before {
+  content: "\e129";
+}
+.glyphicon-hand-down:before {
+  content: "\e130";
+}
+.glyphicon-circle-arrow-right:before {
+  content: "\e131";
+}
+.glyphicon-circle-arrow-left:before {
+  content: "\e132";
+}
+.glyphicon-circle-arrow-up:before {
+  content: "\e133";
+}
+.glyphicon-circle-arrow-down:before {
+  content: "\e134";
+}
+.glyphicon-globe:before {
+  content: "\e135";
+}
+.glyphicon-wrench:before {
+  content: "\e136";
+}
+.glyphicon-tasks:before {
+  content: "\e137";
+}
+.glyphicon-filter:before {
+  content: "\e138";
+}
+.glyphicon-briefcase:before {
+  content: "\e139";
+}
+.glyphicon-fullscreen:before {
+  content: "\e140";
+}
+.glyphicon-dashboard:before {
+  content: "\e141";
+}
+.glyphicon-paperclip:before {
+  content: "\e142";
+}
+.glyphicon-heart-empty:before {
+  content: "\e143";
+}
+.glyphicon-link:before {
+  content: "\e144";
+}
+.glyphicon-phone:before {
+  content: "\e145";
+}
+.glyphicon-pushpin:before {
+  content: "\e146";
+}
+.glyphicon-usd:before {
+  content: "\e148";
+}
+.glyphicon-gbp:before {
+  content: "\e149";
+}
+.glyphicon-sort:before {
+  content: "\e150";
+}
+.glyphicon-sort-by-alphabet:before {
+  content: "\e151";
+}
+.glyphicon-sort-by-alphabet-alt:before {
+  content: "\e152";
+}
+.glyphicon-sort-by-order:before {
+  content: "\e153";
+}
+.glyphicon-sort-by-order-alt:before {
+  content: "\e154";
+}
+.glyphicon-sort-by-attributes:before {
+  content: "\e155";
+}
+.glyphicon-sort-by-attributes-alt:before {
+  content: "\e156";
+}
+.glyphicon-unchecked:before {
+  content: "\e157";
+}
+.glyphicon-expand:before {
+  content: "\e158";
+}
+.glyphicon-collapse-down:before {
+  content: "\e159";
+}
+.glyphicon-collapse-up:before {
+  content: "\e160";
+}
+.glyphicon-log-in:before {
+  content: "\e161";
+}
+.glyphicon-flash:before {
+  content: "\e162";
+}
+.glyphicon-log-out:before {
+  content: "\e163";
+}
+.glyphicon-new-window:before {
+  content: "\e164";
+}
+.glyphicon-record:before {
+  content: "\e165";
+}
+.glyphicon-save:before {
+  content: "\e166";
+}
+.glyphicon-open:before {
+  content: "\e167";
+}
+.glyphicon-saved:before {
+  content: "\e168";
+}
+.glyphicon-import:before {
+  content: "\e169";
+}
+.glyphicon-export:before {
+  content: "\e170";
+}
+.glyphicon-send:before {
+  content: "\e171";
+}
+.glyphicon-floppy-disk:before {
+  content: "\e172";
+}
+.glyphicon-floppy-saved:before {
+  content: "\e173";
+}
+.glyphicon-floppy-remove:before {
+  content: "\e174";
+}
+.glyphicon-floppy-save:before {
+  content: "\e175";
+}
+.glyphicon-floppy-open:before {
+  content: "\e176";
+}
+.glyphicon-credit-card:before {
+  content: "\e177";
+}
+.glyphicon-transfer:before {
+  content: "\e178";
+}
+.glyphicon-cutlery:before {
+  content: "\e179";
+}
+.glyphicon-header:before {
+  content: "\e180";
+}
+.glyphicon-compressed:before {
+  content: "\e181";
+}
+.glyphicon-earphone:before {
+  content: "\e182";
+}
+.glyphicon-phone-alt:before {
+  content: "\e183";
+}
+.glyphicon-tower:before {
+  content: "\e184";
+}
+.glyphicon-stats:before {
+  content: "\e185";
+}
+.glyphicon-sd-video:before {
+  content: "\e186";
+}
+.glyphicon-hd-video:before {
+  content: "\e187";
+}
+.glyphicon-subtitles:before {
+  content: "\e188";
+}
+.glyphicon-sound-stereo:before {
+  content: "\e189";
+}
+.glyphicon-sound-dolby:before {
+  content: "\e190";
+}
+.glyphicon-sound-5-1:before {
+  content: "\e191";
+}
+.glyphicon-sound-6-1:before {
+  content: "\e192";
+}
+.glyphicon-sound-7-1:before {
+  content: "\e193";
+}
+.glyphicon-copyright-mark:before {
+  content: "\e194";
+}
+.glyphicon-registration-mark:before {
+  content: "\e195";
+}
+.glyphicon-cloud-download:before {
+  content: "\e197";
+}
+.glyphicon-cloud-upload:before {
+  content: "\e198";
+}
+.glyphicon-tree-conifer:before {
+  content: "\e199";
+}
+.glyphicon-tree-deciduous:before {
+  content: "\e200";
+}
+.glyphicon-cd:before {
+  content: "\e201";
+}
+.glyphicon-save-file:before {
+  content: "\e202";
+}
+.glyphicon-open-file:before {
+  content: "\e203";
+}
+.glyphicon-level-up:before {
+  content: "\e204";
+}
+.glyphicon-copy:before {
+  content: "\e205";
+}
+.glyphicon-paste:before {
+  content: "\e206";
+}
+.glyphicon-alert:before {
+  content: "\e209";
+}
+.glyphicon-equalizer:before {
+  content: "\e210";
+}
+.glyphicon-king:before {
+  content: "\e211";
+}
+.glyphicon-queen:before {
+  content: "\e212";
+}
+.glyphicon-pawn:before {
+  content: "\e213";
+}
+.glyphicon-bishop:before {
+  content: "\e214";
+}
+.glyphicon-knight:before {
+  content: "\e215";
+}
+.glyphicon-baby-formula:before {
+  content: "\e216";
+}
+.glyphicon-tent:before {
+  content: "\26fa";
+}
+.glyphicon-blackboard:before {
+  content: "\e218";
+}
+.glyphicon-bed:before {
+  content: "\e219";
+}
+.glyphicon-apple:before {
+  content: "\f8ff";
+}
+.glyphicon-erase:before {
+  content: "\e221";
+}
+.glyphicon-hourglass:before {
+  content: "\231b";
+}
+.glyphicon-lamp:before {
+  content: "\e223";
+}
+.glyphicon-duplicate:before {
+  content: "\e224";
+}
+.glyphicon-piggy-bank:before {
+  content: "\e225";
+}
+.glyphicon-scissors:before {
+  content: "\e226";
+}
+.glyphicon-bitcoin:before {
+  content: "\e227";
+}
+.glyphicon-btc:before {
+  content: "\e227";
+}
+.glyphicon-xbt:before {
+  content: "\e227";
+}
+.glyphicon-yen:before {
+  content: "\00a5";
+}
+.glyphicon-jpy:before {
+  content: "\00a5";
+}
+.glyphicon-ruble:before {
+  content: "\20bd";
+}
+.glyphicon-rub:before {
+  content: "\20bd";
+}
+.glyphicon-scale:before {
+  content: "\e230";
+}
+.glyphicon-ice-lolly:before {
+  content: "\e231";
+}
+.glyphicon-ice-lolly-tasted:before {
+  content: "\e232";
+}
+.glyphicon-education:before {
+  content: "\e233";
+}
+.glyphicon-option-horizontal:before {
+  content: "\e234";
+}
+.glyphicon-option-vertical:before {
+  content: "\e235";
+}
+.glyphicon-menu-hamburger:before {
+  content: "\e236";
+}
+.glyphicon-modal-window:before {
+  content: "\e237";
+}
+.glyphicon-oil:before {
+  content: "\e238";
+}
+.glyphicon-grain:before {
+  content: "\e239";
+}
+.glyphicon-sunglasses:before {
+  content: "\e240";
+}
+.glyphicon-text-size:before {
+  content: "\e241";
+}
+.glyphicon-text-color:before {
+  content: "\e242";
+}
+.glyphicon-text-background:before {
+  content: "\e243";
+}
+.glyphicon-object-align-top:before {
+  content: "\e244";
+}
+.glyphicon-object-align-bottom:before {
+  content: "\e245";
+}
+.glyphicon-object-align-horizontal:before {
+  content: "\e246";
+}
+.glyphicon-object-align-left:before {
+  content: "\e247";
+}
+.glyphicon-object-align-vertical:before {
+  content: "\e248";
+}
+.glyphicon-object-align-right:before {
+  content: "\e249";
+}
+.glyphicon-triangle-right:before {
+  content: "\e250";
+}
+.glyphicon-triangle-left:before {
+  content: "\e251";
+}
+.glyphicon-triangle-bottom:before {
+  content: "\e252";
+}
+.glyphicon-triangle-top:before {
+  content: "\e253";
+}
+.glyphicon-console:before {
+  content: "\e254";
+}
+.glyphicon-superscript:before {
+  content: "\e255";
+}
+.glyphicon-subscript:before {
+  content: "\e256";
+}
+.glyphicon-menu-left:before {
+  content: "\e257";
+}
+.glyphicon-menu-right:before {
+  content: "\e258";
+}
+.glyphicon-menu-down:before {
+  content: "\e259";
+}
+.glyphicon-menu-up:before {
+  content: "\e260";
+}
+* {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+html {
+  font-size: 10px;
+
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #333;
+  background-color: #fff;
+}
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+a {
+  color: #337ab7;
+  text-decoration: none;
+}
+a:hover,
+a:focus {
+  color: #23527c;
+  text-decoration: underline;
+}
+a:focus {
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+figure {
+  margin: 0;
+}
+img {
+  vertical-align: middle;
+}
+.img-responsive,
+.thumbnail > img,
+.thumbnail a > img,
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  max-width: 100%;
+  height: auto;
+}
+.img-rounded {
+  border-radius: 6px;
+}
+.img-thumbnail {
+  display: inline-block;
+  max-width: 100%;
+  height: auto;
+  padding: 4px;
+  line-height: 1.42857143;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  -webkit-transition: all .2s ease-in-out;
+       -o-transition: all .2s ease-in-out;
+          transition: all .2s ease-in-out;
+}
+.img-circle {
+  border-radius: 50%;
+}
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eee;
+}
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  clip: auto;
+}
+[role="button"] {
+  cursor: pointer;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: inherit;
+  font-weight: 500;
+  line-height: 1.1;
+  color: inherit;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+  font-weight: normal;
+  line-height: 1;
+  color: #777;
+}
+h1,
+.h1,
+h2,
+.h2,
+h3,
+.h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+h1 small,
+.h1 small,
+h2 small,
+.h2 small,
+h3 small,
+.h3 small,
+h1 .small,
+.h1 .small,
+h2 .small,
+.h2 .small,
+h3 .small,
+.h3 .small {
+  font-size: 65%;
+}
+h4,
+.h4,
+h5,
+.h5,
+h6,
+.h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+h4 small,
+.h4 small,
+h5 small,
+.h5 small,
+h6 small,
+.h6 small,
+h4 .small,
+.h4 .small,
+h5 .small,
+.h5 .small,
+h6 .small,
+.h6 .small {
+  font-size: 75%;
+}
+h1,
+.h1 {
+  font-size: 36px;
+}
+h2,
+.h2 {
+  font-size: 30px;
+}
+h3,
+.h3 {
+  font-size: 24px;
+}
+h4,
+.h4 {
+  font-size: 18px;
+}
+h5,
+.h5 {
+  font-size: 14px;
+}
+h6,
+.h6 {
+  font-size: 12px;
+}
+p {
+  margin: 0 0 10px;
+}
+.lead {
+  margin-bottom: 20px;
+  font-size: 16px;
+  font-weight: 300;
+  line-height: 1.4;
+}
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+small,
+.small {
+  font-size: 85%;
+}
+mark,
+.mark {
+  padding: .2em;
+  background-color: #fcf8e3;
+}
+.text-left {
+  text-align: left;
+}
+.text-right {
+  text-align: right;
+}
+.text-center {
+  text-align: center;
+}
+.text-justify {
+  text-align: justify;
+}
+.text-nowrap {
+  white-space: nowrap;
+}
+.text-lowercase {
+  text-transform: lowercase;
+}
+.text-uppercase {
+  text-transform: uppercase;
+}
+.text-capitalize {
+  text-transform: capitalize;
+}
+.text-muted {
+  color: #777;
+}
+.text-primary {
+  color: #337ab7;
+}
+a.text-primary:hover,
+a.text-primary:focus {
+  color: #286090;
+}
+.text-success {
+  color: #3c763d;
+}
+a.text-success:hover,
+a.text-success:focus {
+  color: #2b542c;
+}
+.text-info {
+  color: #31708f;
+}
+a.text-info:hover,
+a.text-info:focus {
+  color: #245269;
+}
+.text-warning {
+  color: #8a6d3b;
+}
+a.text-warning:hover,
+a.text-warning:focus {
+  color: #66512c;
+}
+.text-danger {
+  color: #a94442;
+}
+a.text-danger:hover,
+a.text-danger:focus {
+  color: #843534;
+}
+.bg-primary {
+  color: #fff;
+  background-color: #337ab7;
+}
+a.bg-primary:hover,
+a.bg-primary:focus {
+  background-color: #286090;
+}
+.bg-success {
+  background-color: #dff0d8;
+}
+a.bg-success:hover,
+a.bg-success:focus {
+  background-color: #c1e2b3;
+}
+.bg-info {
+  background-color: #d9edf7;
+}
+a.bg-info:hover,
+a.bg-info:focus {
+  background-color: #afd9ee;
+}
+.bg-warning {
+  background-color: #fcf8e3;
+}
+a.bg-warning:hover,
+a.bg-warning:focus {
+  background-color: #f7ecb5;
+}
+.bg-danger {
+  background-color: #f2dede;
+}
+a.bg-danger:hover,
+a.bg-danger:focus {
+  background-color: #e4b9b9;
+}
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eee;
+}
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+.list-inline {
+  padding-left: 0;
+  margin-left: -5px;
+  list-style: none;
+}
+.list-inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+dl {
+  margin-top: 0;
+  margin-bottom: 20px;
+}
+dt,
+dd {
+  line-height: 1.42857143;
+}
+dt {
+  font-weight: bold;
+}
+dd {
+  margin-left: 0;
+}
+@media (min-width: 768px) {
+  .dl-horizontal dt {
+    float: left;
+    width: 160px;
+    overflow: hidden;
+    clear: left;
+    text-align: right;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dl-horizontal dd {
+    margin-left: 180px;
+  }
+}
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #777;
+}
+.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  font-size: 17.5px;
+  border-left: 5px solid #eee;
+}
+blockquote p:last-child,
+blockquote ul:last-child,
+blockquote ol:last-child {
+  margin-bottom: 0;
+}
+blockquote footer,
+blockquote small,
+blockquote .small {
+  display: block;
+  font-size: 80%;
+  line-height: 1.42857143;
+  color: #777;
+}
+blockquote footer:before,
+blockquote small:before,
+blockquote .small:before {
+  content: '\2014 \00A0';
+}
+.blockquote-reverse,
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  text-align: right;
+  border-right: 5px solid #eee;
+  border-left: 0;
+}
+.blockquote-reverse footer:before,
+blockquote.pull-right footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right small:before,
+.blockquote-reverse .small:before,
+blockquote.pull-right .small:before {
+  content: '';
+}
+.blockquote-reverse footer:after,
+blockquote.pull-right footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right small:after,
+.blockquote-reverse .small:after,
+blockquote.pull-right .small:after {
+  content: '\00A0 \2014';
+}
+address {
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.42857143;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  background-color: #f9f2f4;
+  border-radius: 4px;
+}
+kbd {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #fff;
+  background-color: #333;
+  border-radius: 3px;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+}
+kbd kbd {
+  padding: 0;
+  font-size: 100%;
+  font-weight: bold;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.42857143;
+  color: #333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+pre code {
+  padding: 0;
+  font-size: inherit;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border-radius: 0;
+}
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+.container {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+@media (min-width: 768px) {
+  .container {
+    width: 750px;
+  }
+}
+@media (min-width: 992px) {
+  .container {
+    width: 970px;
+  }
+}
+@media (min-width: 1200px) {
+  .container {
+    width: 1170px;
+  }
+}
+.container-fluid {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+.row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
+  float: left;
+}
+.col-xs-12 {
+  width: 100%;
+}
+.col-xs-11 {
+  width: 91.66666667%;
+}
+.col-xs-10 {
+  width: 83.33333333%;
+}
+.col-xs-9 {
+  width: 75%;
+}
+.col-xs-8 {
+  width: 66.66666667%;
+}
+.col-xs-7 {
+  width: 58.33333333%;
+}
+.col-xs-6 {
+  width: 50%;
+}
+.col-xs-5 {
+  width: 41.66666667%;
+}
+.col-xs-4 {
+  width: 33.33333333%;
+}
+.col-xs-3 {
+  width: 25%;
+}
+.col-xs-2 {
+  width: 16.66666667%;
+}
+.col-xs-1 {
+  width: 8.33333333%;
+}
+.col-xs-pull-12 {
+  right: 100%;
+}
+.col-xs-pull-11 {
+  right: 91.66666667%;
+}
+.col-xs-pull-10 {
+  right: 83.33333333%;
+}
+.col-xs-pull-9 {
+  right: 75%;
+}
+.col-xs-pull-8 {
+  right: 66.66666667%;
+}
+.col-xs-pull-7 {
+  right: 58.33333333%;
+}
+.col-xs-pull-6 {
+  right: 50%;
+}
+.col-xs-pull-5 {
+  right: 41.66666667%;
+}
+.col-xs-pull-4 {
+  right: 33.33333333%;
+}
+.col-xs-pull-3 {
+  right: 25%;
+}
+.col-xs-pull-2 {
+  right: 16.66666667%;
+}
+.col-xs-pull-1 {
+  right: 8.33333333%;
+}
+.col-xs-pull-0 {
+  right: auto;
+}
+.col-xs-push-12 {
+  left: 100%;
+}
+.col-xs-push-11 {
+  left: 91.66666667%;
+}
+.col-xs-push-10 {
+  left: 83.33333333%;
+}
+.col-xs-push-9 {
+  left: 75%;
+}
+.col-xs-push-8 {
+  left: 66.66666667%;
+}
+.col-xs-push-7 {
+  left: 58.33333333%;
+}
+.col-xs-push-6 {
+  left: 50%;
+}
+.col-xs-push-5 {
+  left: 41.66666667%;
+}
+.col-xs-push-4 {
+  left: 33.33333333%;
+}
+.col-xs-push-3 {
+  left: 25%;
+}
+.col-xs-push-2 {
+  left: 16.66666667%;
+}
+.col-xs-push-1 {
+  left: 8.33333333%;
+}
+.col-xs-push-0 {
+  left: auto;
+}
+.col-xs-offset-12 {
+  margin-left: 100%;
+}
+.col-xs-offset-11 {
+  margin-left: 91.66666667%;
+}
+.col-xs-offset-10 {
+  margin-left: 83.33333333%;
+}
+.col-xs-offset-9 {
+  margin-left: 75%;
+}
+.col-xs-offset-8 {
+  margin-left: 66.66666667%;
+}
+.col-xs-offset-7 {
+  margin-left: 58.33333333%;
+}
+.col-xs-offset-6 {
+  margin-left: 50%;
+}
+.col-xs-offset-5 {
+  margin-left: 41.66666667%;
+}
+.col-xs-offset-4 {
+  margin-left: 33.33333333%;
+}
+.col-xs-offset-3 {
+  margin-left: 25%;
+}
+.col-xs-offset-2 {
+  margin-left: 16.66666667%;
+}
+.col-xs-offset-1 {
+  margin-left: 8.33333333%;
+}
+.col-xs-offset-0 {
+  margin-left: 0;
+}
+@media (min-width: 768px) {
+  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
+    float: left;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-11 {
+    width: 91.66666667%;
+  }
+  .col-sm-10 {
+    width: 83.33333333%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-8 {
+    width: 66.66666667%;
+  }
+  .col-sm-7 {
+    width: 58.33333333%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-5 {
+    width: 41.66666667%;
+  }
+  .col-sm-4 {
+    width: 33.33333333%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-2 {
+    width: 16.66666667%;
+  }
+  .col-sm-1 {
+    width: 8.33333333%;
+  }
+  .col-sm-pull-12 {
+    right: 100%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-sm-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-sm-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-sm-pull-0 {
+    right: auto;
+  }
+  .col-sm-push-12 {
+    left: 100%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666667%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666667%;
+  }
+  .col-sm-push-7 {
+    left: 58.33333333%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666667%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-2 {
+    left: 16.66666667%;
+  }
+  .col-sm-push-1 {
+    left: 8.33333333%;
+  }
+  .col-sm-push-0 {
+    left: auto;
+  }
+  .col-sm-offset-12 {
+    margin-left: 100%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-sm-offset-0 {
+    margin-left: 0;
+  }
+}
+@media (min-width: 992px) {
+  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
+    float: left;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-11 {
+    width: 91.66666667%;
+  }
+  .col-md-10 {
+    width: 83.33333333%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-8 {
+    width: 66.66666667%;
+  }
+  .col-md-7 {
+    width: 58.33333333%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-5 {
+    width: 41.66666667%;
+  }
+  .col-md-4 {
+    width: 33.33333333%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-2 {
+    width: 16.66666667%;
+  }
+  .col-md-1 {
+    width: 8.33333333%;
+  }
+  .col-md-pull-12 {
+    right: 100%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-md-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-md-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-md-pull-0 {
+    right: auto;
+  }
+  .col-md-push-12 {
+    left: 100%;
+  }
+  .col-md-push-11 {
+    left: 91.66666667%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-8 {
+    left: 66.66666667%;
+  }
+  .col-md-push-7 {
+    left: 58.33333333%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-5 {
+    left: 41.66666667%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-2 {
+    left: 16.66666667%;
+  }
+  .col-md-push-1 {
+    left: 8.33333333%;
+  }
+  .col-md-push-0 {
+    left: auto;
+  }
+  .col-md-offset-12 {
+    margin-left: 100%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-md-offset-0 {
+    margin-left: 0;
+  }
+}
+@media (min-width: 1200px) {
+  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
+    float: left;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-11 {
+    width: 91.66666667%;
+  }
+  .col-lg-10 {
+    width: 83.33333333%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-8 {
+    width: 66.66666667%;
+  }
+  .col-lg-7 {
+    width: 58.33333333%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-5 {
+    width: 41.66666667%;
+  }
+  .col-lg-4 {
+    width: 33.33333333%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-2 {
+    width: 16.66666667%;
+  }
+  .col-lg-1 {
+    width: 8.33333333%;
+  }
+  .col-lg-pull-12 {
+    right: 100%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-lg-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-lg-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-lg-pull-0 {
+    right: auto;
+  }
+  .col-lg-push-12 {
+    left: 100%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666667%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666667%;
+  }
+  .col-lg-push-7 {
+    left: 58.33333333%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666667%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-2 {
+    left: 16.66666667%;
+  }
+  .col-lg-push-1 {
+    left: 8.33333333%;
+  }
+  .col-lg-push-0 {
+    left: auto;
+  }
+  .col-lg-offset-12 {
+    margin-left: 100%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-lg-offset-0 {
+    margin-left: 0;
+  }
+}
+table {
+  background-color: transparent;
+}
+caption {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  color: #777;
+  text-align: left;
+}
+th {
+  text-align: left;
+}
+.table {
+  width: 100%;
+  max-width: 100%;
+  margin-bottom: 20px;
+}
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.42857143;
+  vertical-align: top;
+  border-top: 1px solid #ddd;
+}
+.table > thead > tr > th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #ddd;
+}
+.table > caption + thead > tr:first-child > th,
+.table > colgroup + thead > tr:first-child > th,
+.table > thead:first-child > tr:first-child > th,
+.table > caption + thead > tr:first-child > td,
+.table > colgroup + thead > tr:first-child > td,
+.table > thead:first-child > tr:first-child > td {
+  border-top: 0;
+}
+.table > tbody + tbody {
+  border-top: 2px solid #ddd;
+}
+.table .table {
+  background-color: #fff;
+}
+.table-condensed > thead > tr > th,
+.table-condensed > tbody > tr > th,
+.table-condensed > tfoot > tr > th,
+.table-condensed > thead > tr > td,
+.table-condensed > tbody > tr > td,
+.table-condensed > tfoot > tr > td {
+  padding: 5px;
+}
+.table-bordered {
+  border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+  border-bottom-width: 2px;
+}
+.table-striped > tbody > tr:nth-of-type(odd) {
+  background-color: #f9f9f9;
+}
+.table-hover > tbody > tr:hover {
+  background-color: #f5f5f5;
+}
+table col[class*="col-"] {
+  position: static;
+  display: table-column;
+  float: none;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+  position: static;
+  display: table-cell;
+  float: none;
+}
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+.table-hover > tbody > tr > td.active:hover,
+.table-hover > tbody > tr > th.active:hover,
+.table-hover > tbody > tr.active:hover > td,
+.table-hover > tbody > tr:hover > .active,
+.table-hover > tbody > tr.active:hover > th {
+  background-color: #e8e8e8;
+}
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+}
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td,
+.table-hover > tbody > tr:hover > .success,
+.table-hover > tbody > tr.success:hover > th {
+  background-color: #d0e9c6;
+}
+.table > thead > tr > td.info,
+.table > tbody > tr > td.info,
+.table > tfoot > tr > td.info,
+.table > thead > tr > th.info,
+.table > tbody > tr > th.info,
+.table > tfoot > tr > th.info,
+.table > thead > tr.info > td,
+.table > tbody > tr.info > td,
+.table > tfoot > tr.info > td,
+.table > thead > tr.info > th,
+.table > tbody > tr.info > th,
+.table > tfoot > tr.info > th {
+  background-color: #d9edf7;
+}
+.table-hover > tbody > tr > td.info:hover,
+.table-hover > tbody > tr > th.info:hover,
+.table-hover > tbody > tr.info:hover > td,
+.table-hover > tbody > tr:hover > .info,
+.table-hover > tbody > tr.info:hover > th {
+  background-color: #c4e3f3;
+}
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+}
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td,
+.table-hover > tbody > tr:hover > .warning,
+.table-hover > tbody > tr.warning:hover > th {
+  background-color: #faf2cc;
+}
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+}
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td,
+.table-hover > tbody > tr:hover > .danger,
+.table-hover > tbody > tr.danger:hover > th {
+  background-color: #ebcccc;
+}
+.table-responsive {
+  min-height: .01%;
+  overflow-x: auto;
+}
+@media screen and (max-width: 767px) {
+  .table-responsive {
+    width: 100%;
+    margin-bottom: 15px;
+    overflow-y: hidden;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    border: 1px solid #ddd;
+  }
+  .table-responsive > .table {
+    margin-bottom: 0;
+  }
+  .table-responsive > .table > thead > tr > th,
+  .table-responsive > .table > tbody > tr > th,
+  .table-responsive > .table > tfoot > tr > th,
+  .table-responsive > .table > thead > tr > td,
+  .table-responsive > .table > tbody > tr > td,
+  .table-responsive > .table > tfoot > tr > td {
+    white-space: nowrap;
+  }
+  .table-responsive > .table-bordered {
+    border: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:first-child,
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+  .table-responsive > .table-bordered > thead > tr > td:first-child,
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+    border-left: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:last-child,
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+  .table-responsive > .table-bordered > thead > tr > td:last-child,
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+    border-right: 0;
+  }
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+    border-bottom: 0;
+  }
+}
+fieldset {
+  min-width: 0;
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+label {
+  display: inline-block;
+  max-width: 100%;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  line-height: normal;
+}
+input[type="file"] {
+  display: block;
+}
+input[type="range"] {
+  display: block;
+  width: 100%;
+}
+select[multiple],
+select[size] {
+  height: auto;
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+output {
+  display: block;
+  padding-top: 7px;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #555;
+}
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #555;
+  background-color: #fff;
+  background-image: none;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
+       -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+          transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+          box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+}
+.form-control::-moz-placeholder {
+  color: #999;
+  opacity: 1;
+}
+.form-control:-ms-input-placeholder {
+  color: #999;
+}
+.form-control::-webkit-input-placeholder {
+  color: #999;
+}
+.form-control::-ms-expand {
+  background-color: transparent;
+  border: 0;
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  background-color: #eee;
+  opacity: 1;
+}
+.form-control[disabled],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+}
+textarea.form-control {
+  height: auto;
+}
+input[type="search"] {
+  -webkit-appearance: none;
+}
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+  input[type="date"].form-control,
+  input[type="time"].form-control,
+  input[type="datetime-local"].form-control,
+  input[type="month"].form-control {
+    line-height: 34px;
+  }
+  input[type="date"].input-sm,
+  input[type="time"].input-sm,
+  input[type="datetime-local"].input-sm,
+  input[type="month"].input-sm,
+  .input-group-sm input[type="date"],
+  .input-group-sm input[type="time"],
+  .input-group-sm input[type="datetime-local"],
+  .input-group-sm input[type="month"] {
+    line-height: 30px;
+  }
+  input[type="date"].input-lg,
+  input[type="time"].input-lg,
+  input[type="datetime-local"].input-lg,
+  input[type="month"].input-lg,
+  .input-group-lg input[type="date"],
+  .input-group-lg input[type="time"],
+  .input-group-lg input[type="datetime-local"],
+  .input-group-lg input[type="month"] {
+    line-height: 46px;
+  }
+}
+.form-group {
+  margin-bottom: 15px;
+}
+.radio,
+.checkbox {
+  position: relative;
+  display: block;
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.radio label,
+.checkbox label {
+  min-height: 20px;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  position: absolute;
+  margin-top: 4px \9;
+  margin-left: -20px;
+}
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+.radio-inline,
+.checkbox-inline {
+  position: relative;
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  vertical-align: middle;
+  cursor: pointer;
+}
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"].disabled,
+input[type="checkbox"].disabled,
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"] {
+  cursor: not-allowed;
+}
+.radio-inline.disabled,
+.checkbox-inline.disabled,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox-inline {
+  cursor: not-allowed;
+}
+.radio.disabled label,
+.checkbox.disabled label,
+fieldset[disabled] .radio label,
+fieldset[disabled] .checkbox label {
+  cursor: not-allowed;
+}
+.form-control-static {
+  min-height: 34px;
+  padding-top: 7px;
+  padding-bottom: 7px;
+  margin-bottom: 0;
+}
+.form-control-static.input-lg,
+.form-control-static.input-sm {
+  padding-right: 0;
+  padding-left: 0;
+}
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+textarea.input-sm,
+select[multiple].input-sm {
+  height: auto;
+}
+.form-group-sm .form-control {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.form-group-sm select.form-control {
+  height: 30px;
+  line-height: 30px;
+}
+.form-group-sm textarea.form-control,
+.form-group-sm select[multiple].form-control {
+  height: auto;
+}
+.form-group-sm .form-control-static {
+  height: 30px;
+  min-height: 32px;
+  padding: 6px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.input-lg {
+  height: 46px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+select.input-lg {
+  height: 46px;
+  line-height: 46px;
+}
+textarea.input-lg,
+select[multiple].input-lg {
+  height: auto;
+}
+.form-group-lg .form-control {
+  height: 46px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+.form-group-lg select.form-control {
+  height: 46px;
+  line-height: 46px;
+}
+.form-group-lg textarea.form-control,
+.form-group-lg select[multiple].form-control {
+  height: auto;
+}
+.form-group-lg .form-control-static {
+  height: 46px;
+  min-height: 38px;
+  padding: 11px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+}
+.has-feedback {
+  position: relative;
+}
+.has-feedback .form-control {
+  padding-right: 42.5px;
+}
+.form-control-feedback {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 2;
+  display: block;
+  width: 34px;
+  height: 34px;
+  line-height: 34px;
+  text-align: center;
+  pointer-events: none;
+}
+.input-lg + .form-control-feedback,
+.input-group-lg + .form-control-feedback,
+.form-group-lg .form-control + .form-control-feedback {
+  width: 46px;
+  height: 46px;
+  line-height: 46px;
+}
+.input-sm + .form-control-feedback,
+.input-group-sm + .form-control-feedback,
+.form-group-sm .form-control + .form-control-feedback {
+  width: 30px;
+  height: 30px;
+  line-height: 30px;
+}
+.has-success .help-block,
+.has-success .control-label,
+.has-success .radio,
+.has-success .checkbox,
+.has-success .radio-inline,
+.has-success .checkbox-inline,
+.has-success.radio label,
+.has-success.checkbox label,
+.has-success.radio-inline label,
+.has-success.checkbox-inline label {
+  color: #3c763d;
+}
+.has-success .form-control {
+  border-color: #3c763d;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-success .form-control:focus {
+  border-color: #2b542c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+}
+.has-success .input-group-addon {
+  color: #3c763d;
+  background-color: #dff0d8;
+  border-color: #3c763d;
+}
+.has-success .form-control-feedback {
+  color: #3c763d;
+}
+.has-warning .help-block,
+.has-warning .control-label,
+.has-warning .radio,
+.has-warning .checkbox,
+.has-warning .radio-inline,
+.has-warning .checkbox-inline,
+.has-warning.radio label,
+.has-warning.checkbox label,
+.has-warning.radio-inline label,
+.has-warning.checkbox-inline label {
+  color: #8a6d3b;
+}
+.has-warning .form-control {
+  border-color: #8a6d3b;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-warning .form-control:focus {
+  border-color: #66512c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+}
+.has-warning .input-group-addon {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #8a6d3b;
+}
+.has-warning .form-control-feedback {
+  color: #8a6d3b;
+}
+.has-error .help-block,
+.has-error .control-label,
+.has-error .radio,
+.has-error .checkbox,
+.has-error .radio-inline,
+.has-error .checkbox-inline,
+.has-error.radio label,
+.has-error.checkbox label,
+.has-error.radio-inline label,
+.has-error.checkbox-inline label {
+  color: #a94442;
+}
+.has-error .form-control {
+  border-color: #a94442;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-error .form-control:focus {
+  border-color: #843534;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+}
+.has-error .input-group-addon {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #a94442;
+}
+.has-error .form-control-feedback {
+  color: #a94442;
+}
+.has-feedback label ~ .form-control-feedback {
+  top: 25px;
+}
+.has-feedback label.sr-only ~ .form-control-feedback {
+  top: 0;
+}
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+@media (min-width: 768px) {
+  .form-inline .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+    width: auto;
+    vertical-align: middle;
+  }
+  .form-inline .form-control-static {
+    display: inline-block;
+  }
+  .form-inline .input-group {
+    display: inline-table;
+    vertical-align: middle;
+  }
+  .form-inline .input-group .input-group-addon,
+  .form-inline .input-group .input-group-btn,
+  .form-inline .input-group .form-control {
+    width: auto;
+  }
+  .form-inline .input-group > .form-control {
+    width: 100%;
+  }
+  .form-inline .control-label {
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .radio,
+  .form-inline .checkbox {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .radio label,
+  .form-inline .checkbox label {
+    padding-left: 0;
+  }
+  .form-inline .radio input[type="radio"],
+  .form-inline .checkbox input[type="checkbox"] {
+    position: relative;
+    margin-left: 0;
+  }
+  .form-inline .has-feedback .form-control-feedback {
+    top: 0;
+  }
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  padding-top: 7px;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox {
+  min-height: 27px;
+}
+.form-horizontal .form-group {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    padding-top: 7px;
+    margin-bottom: 0;
+    text-align: right;
+  }
+}
+.form-horizontal .has-feedback .form-control-feedback {
+  right: 15px;
+}
+@media (min-width: 768px) {
+  .form-horizontal .form-group-lg .control-label {
+    padding-top: 11px;
+    font-size: 18px;
+  }
+}
+@media (min-width: 768px) {
+  .form-horizontal .form-group-sm .control-label {
+    padding-top: 6px;
+    font-size: 12px;
+  }
+}
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.42857143;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  -ms-touch-action: manipulation;
+      touch-action: manipulation;
+  cursor: pointer;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+  background-image: none;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.btn:focus,
+.btn:active:focus,
+.btn.active:focus,
+.btn.focus,
+.btn:active.focus,
+.btn.active.focus {
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.btn:hover,
+.btn:focus,
+.btn.focus {
+  color: #333;
+  text-decoration: none;
+}
+.btn:active,
+.btn.active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  cursor: not-allowed;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+          box-shadow: none;
+  opacity: .65;
+}
+a.btn.disabled,
+fieldset[disabled] a.btn {
+  pointer-events: none;
+}
+.btn-default {
+  color: #333;
+  background-color: #fff;
+  border-color: #ccc;
+}
+.btn-default:focus,
+.btn-default.focus {
+  color: #333;
+  background-color: #e6e6e6;
+  border-color: #8c8c8c;
+}
+.btn-default:hover {
+  color: #333;
+  background-color: #e6e6e6;
+  border-color: #adadad;
+}
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+  color: #333;
+  background-color: #e6e6e6;
+  border-color: #adadad;
+}
+.btn-default:active:hover,
+.btn-default.active:hover,
+.open > .dropdown-toggle.btn-default:hover,
+.btn-default:active:focus,
+.btn-default.active:focus,
+.open > .dropdown-toggle.btn-default:focus,
+.btn-default:active.focus,
+.btn-default.active.focus,
+.open > .dropdown-toggle.btn-default.focus {
+  color: #333;
+  background-color: #d4d4d4;
+  border-color: #8c8c8c;
+}
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+  background-image: none;
+}
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled.focus,
+.btn-default[disabled].focus,
+fieldset[disabled] .btn-default.focus {
+  background-color: #fff;
+  border-color: #ccc;
+}
+.btn-default .badge {
+  color: #fff;
+  background-color: #333;
+}
+.btn-primary {
+  color: #fff;
+  background-color: #337ab7;
+  border-color: #2e6da4;
+}
+.btn-primary:focus,
+.btn-primary.focus {
+  color: #fff;
+  background-color: #286090;
+  border-color: #122b40;
+}
+.btn-primary:hover {
+  color: #fff;
+  background-color: #286090;
+  border-color: #204d74;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+  color: #fff;
+  background-color: #286090;
+  border-color: #204d74;
+}
+.btn-primary:active:hover,
+.btn-primary.active:hover,
+.open > .dropdown-toggle.btn-primary:hover,
+.btn-primary:active:focus,
+.btn-primary.active:focus,
+.open > .dropdown-toggle.btn-primary:focus,
+.btn-primary:active.focus,
+.btn-primary.active.focus,
+.open > .dropdown-toggle.btn-primary.focus {
+  color: #fff;
+  background-color: #204d74;
+  border-color: #122b40;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled.focus,
+.btn-primary[disabled].focus,
+fieldset[disabled] .btn-primary.focus {
+  background-color: #337ab7;
+  border-color: #2e6da4;
+}
+.btn-primary .badge {
+  color: #337ab7;
+  background-color: #fff;
+}
+.btn-success {
+  color: #fff;
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+.btn-success:focus,
+.btn-success.focus {
+  color: #fff;
+  background-color: #449d44;
+  border-color: #255625;
+}
+.btn-success:hover {
+  color: #fff;
+  background-color: #449d44;
+  border-color: #398439;
+}
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+  color: #fff;
+  background-color: #449d44;
+  border-color: #398439;
+}
+.btn-success:active:hover,
+.btn-success.active:hover,
+.open > .dropdown-toggle.btn-success:hover,
+.btn-success:active:focus,
+.btn-success.active:focus,
+.open > .dropdown-toggle.btn-success:focus,
+.btn-success:active.focus,
+.btn-success.active.focus,
+.open > .dropdown-toggle.btn-success.focus {
+  color: #fff;
+  background-color: #398439;
+  border-color: #255625;
+}
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+  background-image: none;
+}
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled.focus,
+.btn-success[disabled].focus,
+fieldset[disabled] .btn-success.focus {
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+.btn-success .badge {
+  color: #5cb85c;
+  background-color: #fff;
+}
+.btn-info {
+  color: #fff;
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+.btn-info:focus,
+.btn-info.focus {
+  color: #fff;
+  background-color: #31b0d5;
+  border-color: #1b6d85;
+}
+.btn-info:hover {
+  color: #fff;
+  background-color: #31b0d5;
+  border-color: #269abc;
+}
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+  color: #fff;
+  background-color: #31b0d5;
+  border-color: #269abc;
+}
+.btn-info:active:hover,
+.btn-info.active:hover,
+.open > .dropdown-toggle.btn-info:hover,
+.btn-info:active:focus,
+.btn-info.active:focus,
+.open > .dropdown-toggle.btn-info:focus,
+.btn-info:active.focus,
+.btn-info.active.focus,
+.open > .dropdown-toggle.btn-info.focus {
+  color: #fff;
+  background-color: #269abc;
+  border-color: #1b6d85;
+}
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+  background-image: none;
+}
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled.focus,
+.btn-info[disabled].focus,
+fieldset[disabled] .btn-info.focus {
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+.btn-info .badge {
+  color: #5bc0de;
+  background-color: #fff;
+}
+.btn-warning {
+  color: #fff;
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+.btn-warning:focus,
+.btn-warning.focus {
+  color: #fff;
+  background-color: #ec971f;
+  border-color: #985f0d;
+}
+.btn-warning:hover {
+  color: #fff;
+  background-color: #ec971f;
+  border-color: #d58512;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+  color: #fff;
+  background-color: #ec971f;
+  border-color: #d58512;
+}
+.btn-warning:active:hover,
+.btn-warning.active:hover,
+.open > .dropdown-toggle.btn-warning:hover,
+.btn-warning:active:focus,
+.btn-warning.active:focus,
+.open > .dropdown-toggle.btn-warning:focus,
+.btn-warning:active.focus,
+.btn-warning.active.focus,
+.open > .dropdown-toggle.btn-warning.focus {
+  color: #fff;
+  background-color: #d58512;
+  border-color: #985f0d;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+  background-image: none;
+}
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled.focus,
+.btn-warning[disabled].focus,
+fieldset[disabled] .btn-warning.focus {
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+.btn-warning .badge {
+  color: #f0ad4e;
+  background-color: #fff;
+}
+.btn-danger {
+  color: #fff;
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+.btn-danger:focus,
+.btn-danger.focus {
+  color: #fff;
+  background-color: #c9302c;
+  border-color: #761c19;
+}
+.btn-danger:hover {
+  color: #fff;
+  background-color: #c9302c;
+  border-color: #ac2925;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+  color: #fff;
+  background-color: #c9302c;
+  border-color: #ac2925;
+}
+.btn-danger:active:hover,
+.btn-danger.active:hover,
+.open > .dropdown-toggle.btn-danger:hover,
+.btn-danger:active:focus,
+.btn-danger.active:focus,
+.open > .dropdown-toggle.btn-danger:focus,
+.btn-danger:active.focus,
+.btn-danger.active.focus,
+.open > .dropdown-toggle.btn-danger.focus {
+  color: #fff;
+  background-color: #ac2925;
+  border-color: #761c19;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+  background-image: none;
+}
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled.focus,
+.btn-danger[disabled].focus,
+fieldset[disabled] .btn-danger.focus {
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+.btn-danger .badge {
+  color: #d9534f;
+  background-color: #fff;
+}
+.btn-link {
+  font-weight: normal;
+  color: #337ab7;
+  border-radius: 0;
+}
+.btn-link,
+.btn-link:active,
+.btn-link.active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+.btn-link:hover,
+.btn-link:focus {
+  color: #23527c;
+  text-decoration: underline;
+  background-color: transparent;
+}
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #777;
+  text-decoration: none;
+}
+.btn-lg,
+.btn-group-lg > .btn {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+.btn-sm,
+.btn-group-sm > .btn {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.btn-xs,
+.btn-group-xs > .btn {
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.btn-block {
+  display: block;
+  width: 100%;
+}
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity .15s linear;
+       -o-transition: opacity .15s linear;
+          transition: opacity .15s linear;
+}
+.fade.in {
+  opacity: 1;
+}
+.collapse {
+  display: none;
+}
+.collapse.in {
+  display: block;
+}
+tr.collapse.in {
+  display: table-row;
+}
+tbody.collapse.in {
+  display: table-row-group;
+}
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition-timing-function: ease;
+       -o-transition-timing-function: ease;
+          transition-timing-function: ease;
+  -webkit-transition-duration: .35s;
+       -o-transition-duration: .35s;
+          transition-duration: .35s;
+  -webkit-transition-property: height, visibility;
+       -o-transition-property: height, visibility;
+          transition-property: height, visibility;
+}
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top: 4px dashed;
+  border-top: 4px solid \9;
+  border-right: 4px solid transparent;
+  border-left: 4px solid transparent;
+}
+.dropup,
+.dropdown {
+  position: relative;
+}
+.dropdown-toggle:focus {
+  outline: 0;
+}
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  font-size: 14px;
+  text-align: left;
+  list-style: none;
+  background-color: #fff;
+  -webkit-background-clip: padding-box;
+          background-clip: padding-box;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, .15);
+  border-radius: 4px;
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+          box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+}
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 1.42857143;
+  color: #333;
+  white-space: nowrap;
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: #262626;
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #fff;
+  text-decoration: none;
+  background-color: #337ab7;
+  outline: 0;
+}
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #777;
+}
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.open > .dropdown-menu {
+  display: block;
+}
+.open > a {
+  outline: 0;
+}
+.dropdown-menu-right {
+  right: 0;
+  left: auto;
+}
+.dropdown-menu-left {
+  right: auto;
+  left: 0;
+}
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: 12px;
+  line-height: 1.42857143;
+  color: #777;
+  white-space: nowrap;
+}
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  content: "";
+  border-top: 0;
+  border-bottom: 4px dashed;
+  border-bottom: 4px solid \9;
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 2px;
+}
+@media (min-width: 768px) {
+  .navbar-right .dropdown-menu {
+    right: 0;
+    left: auto;
+  }
+  .navbar-right .dropdown-menu-left {
+    right: auto;
+    left: 0;
+  }
+}
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  float: left;
+}
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+  z-index: 2;
+}
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+  margin-left: -1px;
+}
+.btn-toolbar {
+  margin-left: -5px;
+}
+.btn-toolbar .btn,
+.btn-toolbar .btn-group,
+.btn-toolbar .input-group {
+  float: left;
+}
+.btn-toolbar > .btn,
+.btn-toolbar > .btn-group,
+.btn-toolbar > .input-group {
+  margin-left: 5px;
+}
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+.btn-group > .btn:first-child {
+  margin-left: 0;
+}
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group > .btn-group {
+  float: left;
+}
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+.btn-group > .btn + .dropdown-toggle {
+  padding-right: 8px;
+  padding-left: 8px;
+}
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-right: 12px;
+  padding-left: 12px;
+}
+.btn-group.open .dropdown-toggle {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-group.open .dropdown-toggle.btn-link {
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.btn .caret {
+  margin-left: 0;
+}
+.btn-lg .caret {
+  border-width: 5px 5px 0;
+  border-bottom-width: 0;
+}
+.dropup .btn-lg .caret {
+  border-width: 0 5px 5px;
+}
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group,
+.btn-group-vertical > .btn-group > .btn {
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+}
+.btn-group-vertical > .btn-group > .btn {
+  float: none;
+}
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+  margin-top: -1px;
+  margin-left: 0;
+}
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+  border-collapse: separate;
+}
+.btn-group-justified > .btn,
+.btn-group-justified > .btn-group {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+.btn-group-justified > .btn-group .btn {
+  width: 100%;
+}
+.btn-group-justified > .btn-group .dropdown-menu {
+  left: auto;
+}
+[data-toggle="buttons"] > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn input[type="checkbox"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] {
+  position: absolute;
+  clip: rect(0, 0, 0, 0);
+  pointer-events: none;
+}
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+.input-group[class*="col-"] {
+  float: none;
+  padding-right: 0;
+  padding-left: 0;
+}
+.input-group .form-control {
+  position: relative;
+  z-index: 2;
+  float: left;
+  width: 100%;
+  margin-bottom: 0;
+}
+.input-group .form-control:focus {
+  z-index: 3;
+}
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  height: 46px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+  height: 46px;
+  line-height: 46px;
+}
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn,
+select[multiple].input-group-lg > .form-control,
+select[multiple].input-group-lg > .input-group-addon,
+select[multiple].input-group-lg > .input-group-btn > .btn {
+  height: auto;
+}
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  line-height: 30px;
+}
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn,
+select[multiple].input-group-sm > .form-control,
+select[multiple].input-group-sm > .input-group-addon,
+select[multiple].input-group-sm > .input-group-btn > .btn {
+  height: auto;
+}
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1;
+  color: #555;
+  text-align: center;
+  background-color: #eee;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  border-radius: 6px;
+}
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
+.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.input-group-addon:first-child {
+  border-right: 0;
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child),
+.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.input-group-addon:last-child {
+  border-left: 0;
+}
+.input-group-btn {
+  position: relative;
+  font-size: 0;
+  white-space: nowrap;
+}
+.input-group-btn > .btn {
+  position: relative;
+}
+.input-group-btn > .btn + .btn {
+  margin-left: -1px;
+}
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:focus,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group {
+  margin-right: -1px;
+}
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group {
+  z-index: 2;
+  margin-left: -1px;
+}
+.nav {
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+.nav > li {
+  position: relative;
+  display: block;
+}
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eee;
+}
+.nav > li.disabled > a {
+  color: #777;
+}
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #777;
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+}
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+  background-color: #eee;
+  border-color: #337ab7;
+}
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+.nav > li > a > img {
+  max-width: none;
+}
+.nav-tabs {
+  border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.42857143;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+  border-color: #eee #eee #ddd;
+}
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555;
+  cursor: default;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-bottom-color: transparent;
+}
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+.nav-tabs.nav-justified > li {
+  float: none;
+}
+.nav-tabs.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-tabs.nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+.nav-tabs.nav-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+  border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li > a {
+    border-bottom: 1px solid #ddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs.nav-justified > .active > a,
+  .nav-tabs.nav-justified > .active > a:hover,
+  .nav-tabs.nav-justified > .active > a:focus {
+    border-bottom-color: #fff;
+  }
+}
+.nav-pills > li {
+  float: left;
+}
+.nav-pills > li > a {
+  border-radius: 4px;
+}
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #fff;
+  background-color: #337ab7;
+}
+.nav-stacked > li {
+  float: none;
+}
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+.nav-justified {
+  width: 100%;
+}
+.nav-justified > li {
+  float: none;
+}
+.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+@media (min-width: 768px) {
+  .nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+.nav-tabs-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+  border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+  .nav-tabs-justified > li > a {
+    border-bottom: 1px solid #ddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs-justified > .active > a,
+  .nav-tabs-justified > .active > a:hover,
+  .nav-tabs-justified > .active > a:focus {
+    border-bottom-color: #fff;
+  }
+}
+.tab-content > .tab-pane {
+  display: none;
+}
+.tab-content > .active {
+  display: block;
+}
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.navbar {
+  position: relative;
+  min-height: 50px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+}
+@media (min-width: 768px) {
+  .navbar {
+    border-radius: 4px;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-header {
+    float: left;
+  }
+}
+.navbar-collapse {
+  padding-right: 15px;
+  padding-left: 15px;
+  overflow-x: visible;
+  -webkit-overflow-scrolling: touch;
+  border-top: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+}
+.navbar-collapse.in {
+  overflow-y: auto;
+}
+@media (min-width: 768px) {
+  .navbar-collapse {
+    width: auto;
+    border-top: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+  .navbar-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    padding-bottom: 0;
+    overflow: visible !important;
+  }
+  .navbar-collapse.in {
+    overflow-y: visible;
+  }
+  .navbar-fixed-top .navbar-collapse,
+  .navbar-static-top .navbar-collapse,
+  .navbar-fixed-bottom .navbar-collapse {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+.navbar-fixed-top .navbar-collapse,
+.navbar-fixed-bottom .navbar-collapse {
+  max-height: 340px;
+}
+@media (max-device-width: 480px) and (orientation: landscape) {
+  .navbar-fixed-top .navbar-collapse,
+  .navbar-fixed-bottom .navbar-collapse {
+    max-height: 200px;
+  }
+}
+.container > .navbar-header,
+.container-fluid > .navbar-header,
+.container > .navbar-collapse,
+.container-fluid > .navbar-collapse {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+@media (min-width: 768px) {
+  .container > .navbar-header,
+  .container-fluid > .navbar-header,
+  .container > .navbar-collapse,
+  .container-fluid > .navbar-collapse {
+    margin-right: 0;
+    margin-left: 0;
+  }
+}
+.navbar-static-top {
+  z-index: 1000;
+  border-width: 0 0 1px;
+}
+@media (min-width: 768px) {
+  .navbar-static-top {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+}
+@media (min-width: 768px) {
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top {
+  top: 0;
+  border-width: 0 0 1px;
+}
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+  border-width: 1px 0 0;
+}
+.navbar-brand {
+  float: left;
+  height: 50px;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+}
+.navbar-brand:hover,
+.navbar-brand:focus {
+  text-decoration: none;
+}
+.navbar-brand > img {
+  display: block;
+}
+@media (min-width: 768px) {
+  .navbar > .container .navbar-brand,
+  .navbar > .container-fluid .navbar-brand {
+    margin-left: -15px;
+  }
+}
+.navbar-toggle {
+  position: relative;
+  float: right;
+  padding: 9px 10px;
+  margin-top: 8px;
+  margin-right: 15px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  background-image: none;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.navbar-toggle:focus {
+  outline: 0;
+}
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  border-radius: 1px;
+}
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+@media (min-width: 768px) {
+  .navbar-toggle {
+    display: none;
+  }
+}
+.navbar-nav {
+  margin: 7.5px -15px;
+}
+.navbar-nav > li > a {
+  padding-top: 10px;
+  padding-bottom: 10px;
+  line-height: 20px;
+}
+@media (max-width: 767px) {
+  .navbar-nav .open .dropdown-menu {
+    position: static;
+    float: none;
+    width: auto;
+    margin-top: 0;
+    background-color: transparent;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+  .navbar-nav .open .dropdown-menu > li > a,
+  .navbar-nav .open .dropdown-menu .dropdown-header {
+    padding: 5px 15px 5px 25px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a {
+    line-height: 20px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-nav .open .dropdown-menu > li > a:focus {
+    background-image: none;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-nav {
+    float: left;
+    margin: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+}
+.navbar-form {
+  padding: 10px 15px;
+  margin-top: 8px;
+  margin-right: -15px;
+  margin-bottom: 8px;
+  margin-left: -15px;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+}
+@media (min-width: 768px) {
+  .navbar-form .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control {
+    display: inline-block;
+    width: auto;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control-static {
+    display: inline-block;
+  }
+  .navbar-form .input-group {
+    display: inline-table;
+    vertical-align: middle;
+  }
+  .navbar-form .input-group .input-group-addon,
+  .navbar-form .input-group .input-group-btn,
+  .navbar-form .input-group .form-control {
+    width: auto;
+  }
+  .navbar-form .input-group > .form-control {
+    width: 100%;
+  }
+  .navbar-form .control-label {
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .radio,
+  .navbar-form .checkbox {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .radio label,
+  .navbar-form .checkbox label {
+    padding-left: 0;
+  }
+  .navbar-form .radio input[type="radio"],
+  .navbar-form .checkbox input[type="checkbox"] {
+    position: relative;
+    margin-left: 0;
+  }
+  .navbar-form .has-feedback .form-control-feedback {
+    top: 0;
+  }
+}
+@media (max-width: 767px) {
+  .navbar-form .form-group {
+    margin-bottom: 5px;
+  }
+  .navbar-form .form-group:last-child {
+    margin-bottom: 0;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-form {
+    width: auto;
+    padding-top: 0;
+    padding-bottom: 0;
+    margin-right: 0;
+    margin-left: 0;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+}
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  margin-bottom: 0;
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.navbar-btn {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+.navbar-btn.btn-sm {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.navbar-btn.btn-xs {
+  margin-top: 14px;
+  margin-bottom: 14px;
+}
+.navbar-text {
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+@media (min-width: 768px) {
+  .navbar-text {
+    float: left;
+    margin-right: 15px;
+    margin-left: 15px;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-left {
+    float: left !important;
+  }
+  .navbar-right {
+    float: right !important;
+    margin-right: -15px;
+  }
+  .navbar-right ~ .navbar-right {
+    margin-right: 0;
+  }
+}
+.navbar-default {
+  background-color: #f8f8f8;
+  border-color: #e7e7e7;
+}
+.navbar-default .navbar-brand {
+  color: #777;
+}
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+  color: #5e5e5e;
+  background-color: transparent;
+}
+.navbar-default .navbar-text {
+  color: #777;
+}
+.navbar-default .navbar-nav > li > a {
+  color: #777;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+  color: #333;
+  background-color: transparent;
+}
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+  color: #555;
+  background-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+  color: #ccc;
+  background-color: transparent;
+}
+.navbar-default .navbar-toggle {
+  border-color: #ddd;
+}
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+  background-color: #ddd;
+}
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: #888;
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+  border-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  color: #555;
+  background-color: #e7e7e7;
+}
+@media (max-width: 767px) {
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+    color: #777;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #333;
+    background-color: transparent;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #555;
+    background-color: #e7e7e7;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #ccc;
+    background-color: transparent;
+  }
+}
+.navbar-default .navbar-link {
+  color: #777;
+}
+.navbar-default .navbar-link:hover {
+  color: #333;
+}
+.navbar-default .btn-link {
+  color: #777;
+}
+.navbar-default .btn-link:hover,
+.navbar-default .btn-link:focus {
+  color: #333;
+}
+.navbar-default .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-default .btn-link:hover,
+.navbar-default .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-default .btn-link:focus {
+  color: #ccc;
+}
+.navbar-inverse {
+  background-color: #222;
+  border-color: #080808;
+}
+.navbar-inverse .navbar-brand {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #fff;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-text {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #fff;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #fff;
+  background-color: #080808;
+}
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-toggle {
+  border-color: #333;
+}
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333;
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #fff;
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+  border-color: #101010;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  color: #fff;
+  background-color: #080808;
+}
+@media (max-width: 767px) {
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+    border-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+    color: #9d9d9d;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #fff;
+    background-color: transparent;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #fff;
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #444;
+    background-color: transparent;
+  }
+}
+.navbar-inverse .navbar-link {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-link:hover {
+  color: #fff;
+}
+.navbar-inverse .btn-link {
+  color: #9d9d9d;
+}
+.navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link:focus {
+  color: #fff;
+}
+.navbar-inverse .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-inverse .btn-link:focus {
+  color: #444;
+}
+.breadcrumb {
+  padding: 8px 15px;
+  margin-bottom: 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+.breadcrumb > li {
+  display: inline-block;
+}
+.breadcrumb > li + li:before {
+  padding: 0 5px;
+  color: #ccc;
+  content: "/\00a0";
+}
+.breadcrumb > .active {
+  color: #777;
+}
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: 20px 0;
+  border-radius: 4px;
+}
+.pagination > li {
+  display: inline;
+}
+.pagination > li > a,
+.pagination > li > span {
+  position: relative;
+  float: left;
+  padding: 6px 12px;
+  margin-left: -1px;
+  line-height: 1.42857143;
+  color: #337ab7;
+  text-decoration: none;
+  background-color: #fff;
+  border: 1px solid #ddd;
+}
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+  margin-left: 0;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+  z-index: 2;
+  color: #23527c;
+  background-color: #eee;
+  border-color: #ddd;
+}
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+  z-index: 3;
+  color: #fff;
+  cursor: default;
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.pagination > .disabled > span,
+.pagination > .disabled > span:hover,
+.pagination > .disabled > span:focus,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+  color: #777;
+  cursor: not-allowed;
+  background-color: #fff;
+  border-color: #ddd;
+}
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+}
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+  border-top-left-radius: 6px;
+  border-bottom-left-radius: 6px;
+}
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+  border-top-left-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+  border-top-right-radius: 3px;
+  border-bottom-right-radius: 3px;
+}
+.pager {
+  padding-left: 0;
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+}
+.pager li {
+  display: inline;
+}
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-radius: 15px;
+}
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #eee;
+}
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #777;
+  cursor: not-allowed;
+  background-color: #fff;
+}
+.label {
+  display: inline;
+  padding: .2em .6em .3em;
+  font-size: 75%;
+  font-weight: bold;
+  line-height: 1;
+  color: #fff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+}
+a.label:hover,
+a.label:focus {
+  color: #fff;
+  text-decoration: none;
+  cursor: pointer;
+}
+.label:empty {
+  display: none;
+}
+.btn .label {
+  position: relative;
+  top: -1px;
+}
+.label-default {
+  background-color: #777;
+}
+.label-default[href]:hover,
+.label-default[href]:focus {
+  background-color: #5e5e5e;
+}
+.label-primary {
+  background-color: #337ab7;
+}
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+  background-color: #286090;
+}
+.label-success {
+  background-color: #5cb85c;
+}
+.label-success[href]:hover,
+.label-success[href]:focus {
+  background-color: #449d44;
+}
+.label-info {
+  background-color: #5bc0de;
+}
+.label-info[href]:hover,
+.label-info[href]:focus {
+  background-color: #31b0d5;
+}
+.label-warning {
+  background-color: #f0ad4e;
+}
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+  background-color: #ec971f;
+}
+.label-danger {
+  background-color: #d9534f;
+}
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+  background-color: #c9302c;
+}
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 1;
+  color: #fff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  background-color: #777;
+  border-radius: 10px;
+}
+.badge:empty {
+  display: none;
+}
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+.btn-xs .badge,
+.btn-group-xs > .btn .badge {
+  top: 0;
+  padding: 1px 5px;
+}
+a.badge:hover,
+a.badge:focus {
+  color: #fff;
+  text-decoration: none;
+  cursor: pointer;
+}
+.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+  color: #337ab7;
+  background-color: #fff;
+}
+.list-group-item > .badge {
+  float: right;
+}
+.list-group-item > .badge + .badge {
+  margin-right: 5px;
+}
+.nav-pills > li > a > .badge {
+  margin-left: 3px;
+}
+.jumbotron {
+  padding-top: 30px;
+  padding-bottom: 30px;
+  margin-bottom: 30px;
+  color: inherit;
+  background-color: #eee;
+}
+.jumbotron h1,
+.jumbotron .h1 {
+  color: inherit;
+}
+.jumbotron p {
+  margin-bottom: 15px;
+  font-size: 21px;
+  font-weight: 200;
+}
+.jumbotron > hr {
+  border-top-color: #d5d5d5;
+}
+.container .jumbotron,
+.container-fluid .jumbotron {
+  padding-right: 15px;
+  padding-left: 15px;
+  border-radius: 6px;
+}
+.jumbotron .container {
+  max-width: 100%;
+}
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding-top: 48px;
+    padding-bottom: 48px;
+  }
+  .container .jumbotron,
+  .container-fluid .jumbotron {
+    padding-right: 60px;
+    padding-left: 60px;
+  }
+  .jumbotron h1,
+  .jumbotron .h1 {
+    font-size: 63px;
+  }
+}
+.thumbnail {
+  display: block;
+  padding: 4px;
+  margin-bottom: 20px;
+  line-height: 1.42857143;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  -webkit-transition: border .2s ease-in-out;
+       -o-transition: border .2s ease-in-out;
+          transition: border .2s ease-in-out;
+}
+.thumbnail > img,
+.thumbnail a > img {
+  margin-right: auto;
+  margin-left: auto;
+}
+a.thumbnail:hover,
+a.thumbnail:focus,
+a.thumbnail.active {
+  border-color: #337ab7;
+}
+.thumbnail .caption {
+  padding: 9px;
+  color: #333;
+}
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+.alert .alert-link {
+  font-weight: bold;
+}
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+.alert > p + p {
+  margin-top: 5px;
+}
+.alert-dismissable,
+.alert-dismissible {
+  padding-right: 35px;
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+.alert-success {
+  color: #3c763d;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+.alert-success .alert-link {
+  color: #2b542c;
+}
+.alert-info {
+  color: #31708f;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+.alert-info .alert-link {
+  color: #245269;
+}
+.alert-warning {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+.alert-warning hr {
+  border-top-color: #f7e1b5;
+}
+.alert-warning .alert-link {
+  color: #66512c;
+}
+.alert-danger {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+.alert-danger hr {
+  border-top-color: #e4b9c0;
+}
+.alert-danger .alert-link {
+  color: #843534;
+}
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+}
+.progress-bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  line-height: 20px;
+  color: #fff;
+  text-align: center;
+  background-color: #337ab7;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+  -webkit-transition: width .6s ease;
+       -o-transition: width .6s ease;
+          transition: width .6s ease;
+}
+.progress-striped .progress-bar,
+.progress-bar-striped {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  -webkit-background-size: 40px 40px;
+          background-size: 40px 40px;
+}
+.progress.active .progress-bar,
+.progress-bar.active {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+       -o-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-bar-success {
+  background-color: #5cb85c;
+}
+.progress-striped .progress-bar-success {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-info {
+  background-color: #5bc0de;
+}
+.progress-striped .progress-bar-info {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-warning {
+  background-color: #f0ad4e;
+}
+.progress-striped .progress-bar-warning {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-danger {
+  background-color: #d9534f;
+}
+.progress-striped .progress-bar-danger {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.media {
+  margin-top: 15px;
+}
+.media:first-child {
+  margin-top: 0;
+}
+.media,
+.media-body {
+  overflow: hidden;
+  zoom: 1;
+}
+.media-body {
+  width: 10000px;
+}
+.media-object {
+  display: block;
+}
+.media-object.img-thumbnail {
+  max-width: none;
+}
+.media-right,
+.media > .pull-right {
+  padding-left: 10px;
+}
+.media-left,
+.media > .pull-left {
+  padding-right: 10px;
+}
+.media-left,
+.media-right,
+.media-body {
+  display: table-cell;
+  vertical-align: top;
+}
+.media-middle {
+  vertical-align: middle;
+}
+.media-bottom {
+  vertical-align: bottom;
+}
+.media-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
+.list-group {
+  padding-left: 0;
+  margin-bottom: 20px;
+}
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  margin-bottom: -1px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+}
+.list-group-item:first-child {
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+}
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+a.list-group-item,
+button.list-group-item {
+  color: #555;
+}
+a.list-group-item .list-group-item-heading,
+button.list-group-item .list-group-item-heading {
+  color: #333;
+}
+a.list-group-item:hover,
+button.list-group-item:hover,
+a.list-group-item:focus,
+button.list-group-item:focus {
+  color: #555;
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+button.list-group-item {
+  width: 100%;
+  text-align: left;
+}
+.list-group-item.disabled,
+.list-group-item.disabled:hover,
+.list-group-item.disabled:focus {
+  color: #777;
+  cursor: not-allowed;
+  background-color: #eee;
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading {
+  color: inherit;
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text {
+  color: #777;
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  z-index: 2;
+  color: #fff;
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active .list-group-item-heading > small,
+.list-group-item.active:hover .list-group-item-heading > small,
+.list-group-item.active:focus .list-group-item-heading > small,
+.list-group-item.active .list-group-item-heading > .small,
+.list-group-item.active:hover .list-group-item-heading > .small,
+.list-group-item.active:focus .list-group-item-heading > .small {
+  color: inherit;
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+  color: #c7ddef;
+}
+.list-group-item-success {
+  color: #3c763d;
+  background-color: #dff0d8;
+}
+a.list-group-item-success,
+button.list-group-item-success {
+  color: #3c763d;
+}
+a.list-group-item-success .list-group-item-heading,
+button.list-group-item-success .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-success:hover,
+button.list-group-item-success:hover,
+a.list-group-item-success:focus,
+button.list-group-item-success:focus {
+  color: #3c763d;
+  background-color: #d0e9c6;
+}
+a.list-group-item-success.active,
+button.list-group-item-success.active,
+a.list-group-item-success.active:hover,
+button.list-group-item-success.active:hover,
+a.list-group-item-success.active:focus,
+button.list-group-item-success.active:focus {
+  color: #fff;
+  background-color: #3c763d;
+  border-color: #3c763d;
+}
+.list-group-item-info {
+  color: #31708f;
+  background-color: #d9edf7;
+}
+a.list-group-item-info,
+button.list-group-item-info {
+  color: #31708f;
+}
+a.list-group-item-info .list-group-item-heading,
+button.list-group-item-info .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-info:hover,
+button.list-group-item-info:hover,
+a.list-group-item-info:focus,
+button.list-group-item-info:focus {
+  color: #31708f;
+  background-color: #c4e3f3;
+}
+a.list-group-item-info.active,
+button.list-group-item-info.active,
+a.list-group-item-info.active:hover,
+button.list-group-item-info.active:hover,
+a.list-group-item-info.active:focus,
+button.list-group-item-info.active:focus {
+  color: #fff;
+  background-color: #31708f;
+  border-color: #31708f;
+}
+.list-group-item-warning {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+}
+a.list-group-item-warning,
+button.list-group-item-warning {
+  color: #8a6d3b;
+}
+a.list-group-item-warning .list-group-item-heading,
+button.list-group-item-warning .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-warning:hover,
+button.list-group-item-warning:hover,
+a.list-group-item-warning:focus,
+button.list-group-item-warning:focus {
+  color: #8a6d3b;
+  background-color: #faf2cc;
+}
+a.list-group-item-warning.active,
+button.list-group-item-warning.active,
+a.list-group-item-warning.active:hover,
+button.list-group-item-warning.active:hover,
+a.list-group-item-warning.active:focus,
+button.list-group-item-warning.active:focus {
+  color: #fff;
+  background-color: #8a6d3b;
+  border-color: #8a6d3b;
+}
+.list-group-item-danger {
+  color: #a94442;
+  background-color: #f2dede;
+}
+a.list-group-item-danger,
+button.list-group-item-danger {
+  color: #a94442;
+}
+a.list-group-item-danger .list-group-item-heading,
+button.list-group-item-danger .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-danger:hover,
+button.list-group-item-danger:hover,
+a.list-group-item-danger:focus,
+button.list-group-item-danger:focus {
+  color: #a94442;
+  background-color: #ebcccc;
+}
+a.list-group-item-danger.active,
+button.list-group-item-danger.active,
+a.list-group-item-danger.active:hover,
+button.list-group-item-danger.active:hover,
+a.list-group-item-danger.active:focus,
+button.list-group-item-danger.active:focus {
+  color: #fff;
+  background-color: #a94442;
+  border-color: #a94442;
+}
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+.panel {
+  margin-bottom: 20px;
+  background-color: #fff;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+          box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+}
+.panel-body {
+  padding: 15px;
+}
+.panel-heading {
+  padding: 10px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel-heading > .dropdown .dropdown-toggle {
+  color: inherit;
+}
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 16px;
+  color: inherit;
+}
+.panel-title > a,
+.panel-title > small,
+.panel-title > .small,
+.panel-title > small > a,
+.panel-title > .small > a {
+  color: inherit;
+}
+.panel-footer {
+  padding: 10px 15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #ddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .list-group,
+.panel > .panel-collapse > .list-group {
+  margin-bottom: 0;
+}
+.panel > .list-group .list-group-item,
+.panel > .panel-collapse > .list-group .list-group-item {
+  border-width: 1px 0;
+  border-radius: 0;
+}
+.panel > .list-group:first-child .list-group-item:first-child,
+.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+  border-top: 0;
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel > .list-group:last-child .list-group-item:last-child,
+.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
+  border-bottom: 0;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.panel-heading + .list-group .list-group-item:first-child {
+  border-top-width: 0;
+}
+.list-group + .panel-footer {
+  border-top-width: 0;
+}
+.panel > .table,
+.panel > .table-responsive > .table,
+.panel > .panel-collapse > .table {
+  margin-bottom: 0;
+}
+.panel > .table caption,
+.panel > .table-responsive > .table caption,
+.panel > .panel-collapse > .table caption {
+  padding-right: 15px;
+  padding-left: 15px;
+}
+.panel > .table:first-child,
+.panel > .table-responsive:first-child > .table:first-child {
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
+  border-top-left-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
+  border-top-right-radius: 3px;
+}
+.panel > .table:last-child,
+.panel > .table-responsive:last-child > .table:last-child {
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+  border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+  border-bottom-right-radius: 3px;
+}
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive,
+.panel > .table + .panel-body,
+.panel > .table-responsive + .panel-body {
+  border-top: 1px solid #ddd;
+}
+.panel > .table > tbody:first-child > tr:first-child th,
+.panel > .table > tbody:first-child > tr:first-child td {
+  border-top: 0;
+}
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+  border: 0;
+}
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+  border-left: 0;
+}
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+  border-right: 0;
+}
+.panel > .table-bordered > thead > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,
+.panel > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-bordered > thead > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,
+.panel > .table-bordered > tbody > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {
+  border-bottom: 0;
+}
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {
+  border-bottom: 0;
+}
+.panel > .table-responsive {
+  margin-bottom: 0;
+  border: 0;
+}
+.panel-group {
+  margin-bottom: 20px;
+}
+.panel-group .panel {
+  margin-bottom: 0;
+  border-radius: 4px;
+}
+.panel-group .panel + .panel {
+  margin-top: 5px;
+}
+.panel-group .panel-heading {
+  border-bottom: 0;
+}
+.panel-group .panel-heading + .panel-collapse > .panel-body,
+.panel-group .panel-heading + .panel-collapse > .list-group {
+  border-top: 1px solid #ddd;
+}
+.panel-group .panel-footer {
+  border-top: 0;
+}
+.panel-group .panel-footer + .panel-collapse .panel-body {
+  border-bottom: 1px solid #ddd;
+}
+.panel-default {
+  border-color: #ddd;
+}
+.panel-default > .panel-heading {
+  color: #333;
+  background-color: #f5f5f5;
+  border-color: #ddd;
+}
+.panel-default > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #ddd;
+}
+.panel-default > .panel-heading .badge {
+  color: #f5f5f5;
+  background-color: #333;
+}
+.panel-default > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #ddd;
+}
+.panel-primary {
+  border-color: #337ab7;
+}
+.panel-primary > .panel-heading {
+  color: #fff;
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.panel-primary > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #337ab7;
+}
+.panel-primary > .panel-heading .badge {
+  color: #337ab7;
+  background-color: #fff;
+}
+.panel-primary > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #337ab7;
+}
+.panel-success {
+  border-color: #d6e9c6;
+}
+.panel-success > .panel-heading {
+  color: #3c763d;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+.panel-success > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #d6e9c6;
+}
+.panel-success > .panel-heading .badge {
+  color: #dff0d8;
+  background-color: #3c763d;
+}
+.panel-success > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #d6e9c6;
+}
+.panel-info {
+  border-color: #bce8f1;
+}
+.panel-info > .panel-heading {
+  color: #31708f;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+.panel-info > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #bce8f1;
+}
+.panel-info > .panel-heading .badge {
+  color: #d9edf7;
+  background-color: #31708f;
+}
+.panel-info > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #bce8f1;
+}
+.panel-warning {
+  border-color: #faebcc;
+}
+.panel-warning > .panel-heading {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+.panel-warning > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #faebcc;
+}
+.panel-warning > .panel-heading .badge {
+  color: #fcf8e3;
+  background-color: #8a6d3b;
+}
+.panel-warning > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #faebcc;
+}
+.panel-danger {
+  border-color: #ebccd1;
+}
+.panel-danger > .panel-heading {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+.panel-danger > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #ebccd1;
+}
+.panel-danger > .panel-heading .badge {
+  color: #f2dede;
+  background-color: #a94442;
+}
+.panel-danger > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #ebccd1;
+}
+.embed-responsive {
+  position: relative;
+  display: block;
+  height: 0;
+  padding: 0;
+  overflow: hidden;
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  border: 0;
+}
+.embed-responsive-16by9 {
+  padding-bottom: 56.25%;
+}
+.embed-responsive-4by3 {
+  padding-bottom: 75%;
+}
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+}
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, .15);
+}
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000;
+  text-shadow: 0 1px 0 #fff;
+  filter: alpha(opacity=20);
+  opacity: .2;
+}
+.close:hover,
+.close:focus {
+  color: #000;
+  text-decoration: none;
+  cursor: pointer;
+  filter: alpha(opacity=50);
+  opacity: .5;
+}
+button.close {
+  -webkit-appearance: none;
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+}
+.modal-open {
+  overflow: hidden;
+}
+.modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1050;
+  display: none;
+  overflow: hidden;
+  -webkit-overflow-scrolling: touch;
+  outline: 0;
+}
+.modal.fade .modal-dialog {
+  -webkit-transition: -webkit-transform .3s ease-out;
+       -o-transition:      -o-transform .3s ease-out;
+          transition:         transform .3s ease-out;
+  -webkit-transform: translate(0, -25%);
+      -ms-transform: translate(0, -25%);
+       -o-transform: translate(0, -25%);
+          transform: translate(0, -25%);
+}
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+      -ms-transform: translate(0, 0);
+       -o-transform: translate(0, 0);
+          transform: translate(0, 0);
+}
+.modal-open .modal {
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+.modal-dialog {
+  position: relative;
+  width: auto;
+  margin: 10px;
+}
+.modal-content {
+  position: relative;
+  background-color: #fff;
+  -webkit-background-clip: padding-box;
+          background-clip: padding-box;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, .2);
+  border-radius: 6px;
+  outline: 0;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+          box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+}
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  background-color: #000;
+}
+.modal-backdrop.fade {
+  filter: alpha(opacity=0);
+  opacity: 0;
+}
+.modal-backdrop.in {
+  filter: alpha(opacity=50);
+  opacity: .5;
+}
+.modal-header {
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+}
+.modal-header .close {
+  margin-top: -2px;
+}
+.modal-title {
+  margin: 0;
+  line-height: 1.42857143;
+}
+.modal-body {
+  position: relative;
+  padding: 15px;
+}
+.modal-footer {
+  padding: 15px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+.modal-scrollbar-measure {
+  position: absolute;
+  top: -9999px;
+  width: 50px;
+  height: 50px;
+  overflow: scroll;
+}
+@media (min-width: 768px) {
+  .modal-dialog {
+    width: 600px;
+    margin: 30px auto;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+  }
+  .modal-sm {
+    width: 300px;
+  }
+}
+@media (min-width: 992px) {
+  .modal-lg {
+    width: 900px;
+  }
+}
+.tooltip {
+  position: absolute;
+  z-index: 1070;
+  display: block;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 12px;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1.42857143;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  word-wrap: normal;
+  white-space: normal;
+  filter: alpha(opacity=0);
+  opacity: 0;
+
+  line-break: auto;
+}
+.tooltip.in {
+  filter: alpha(opacity=90);
+  opacity: .9;
+}
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #fff;
+  text-align: center;
+  background-color: #000;
+  border-radius: 4px;
+}
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000;
+}
+.tooltip.top-left .tooltip-arrow {
+  right: 5px;
+  bottom: 0;
+  margin-bottom: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000;
+}
+.tooltip.top-right .tooltip-arrow {
+  bottom: 0;
+  left: 5px;
+  margin-bottom: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000;
+}
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-width: 5px 5px 5px 0;
+  border-right-color: #000;
+}
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-width: 5px 0 5px 5px;
+  border-left-color: #000;
+}
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000;
+}
+.tooltip.bottom-left .tooltip-arrow {
+  top: 0;
+  right: 5px;
+  margin-top: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000;
+}
+.tooltip.bottom-right .tooltip-arrow {
+  top: 0;
+  left: 5px;
+  margin-top: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000;
+}
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1060;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1.42857143;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  word-wrap: normal;
+  white-space: normal;
+  background-color: #fff;
+  -webkit-background-clip: padding-box;
+          background-clip: padding-box;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, .2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+
+  line-break: auto;
+}
+.popover.top {
+  margin-top: -10px;
+}
+.popover.right {
+  margin-left: 10px;
+}
+.popover.bottom {
+  margin-top: 10px;
+}
+.popover.left {
+  margin-left: -10px;
+}
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+.popover-content {
+  padding: 9px 14px;
+}
+.popover > .arrow,
+.popover > .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.popover > .arrow {
+  border-width: 11px;
+}
+.popover > .arrow:after {
+  content: "";
+  border-width: 10px;
+}
+.popover.top > .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999;
+  border-top-color: rgba(0, 0, 0, .25);
+  border-bottom-width: 0;
+}
+.popover.top > .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  content: " ";
+  border-top-color: #fff;
+  border-bottom-width: 0;
+}
+.popover.right > .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999;
+  border-right-color: rgba(0, 0, 0, .25);
+  border-left-width: 0;
+}
+.popover.right > .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  content: " ";
+  border-right-color: #fff;
+  border-left-width: 0;
+}
+.popover.bottom > .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-width: 0;
+  border-bottom-color: #999;
+  border-bottom-color: rgba(0, 0, 0, .25);
+}
+.popover.bottom > .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  content: " ";
+  border-top-width: 0;
+  border-bottom-color: #fff;
+}
+.popover.left > .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-right-width: 0;
+  border-left-color: #999;
+  border-left-color: rgba(0, 0, 0, .25);
+}
+.popover.left > .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  content: " ";
+  border-right-width: 0;
+  border-left-color: #fff;
+}
+.carousel {
+  position: relative;
+}
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: .6s ease-in-out left;
+       -o-transition: .6s ease-in-out left;
+          transition: .6s ease-in-out left;
+}
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  line-height: 1;
+}
+@media all and (transform-3d), (-webkit-transform-3d) {
+  .carousel-inner > .item {
+    -webkit-transition: -webkit-transform .6s ease-in-out;
+         -o-transition:      -o-transform .6s ease-in-out;
+            transition:         transform .6s ease-in-out;
+
+    -webkit-backface-visibility: hidden;
+            backface-visibility: hidden;
+    -webkit-perspective: 1000px;
+            perspective: 1000px;
+  }
+  .carousel-inner > .item.next,
+  .carousel-inner > .item.active.right {
+    left: 0;
+    -webkit-transform: translate3d(100%, 0, 0);
+            transform: translate3d(100%, 0, 0);
+  }
+  .carousel-inner > .item.prev,
+  .carousel-inner > .item.active.left {
+    left: 0;
+    -webkit-transform: translate3d(-100%, 0, 0);
+            transform: translate3d(-100%, 0, 0);
+  }
+  .carousel-inner > .item.next.left,
+  .carousel-inner > .item.prev.right,
+  .carousel-inner > .item.active {
+    left: 0;
+    -webkit-transform: translate3d(0, 0, 0);
+            transform: translate3d(0, 0, 0);
+  }
+}
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+.carousel-inner > .active {
+  left: 0;
+}
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.carousel-inner > .next {
+  left: 100%;
+}
+.carousel-inner > .prev {
+  left: -100%;
+}
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+.carousel-inner > .active.left {
+  left: -100%;
+}
+.carousel-inner > .active.right {
+  left: 100%;
+}
+.carousel-control {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 15%;
+  font-size: 20px;
+  color: #fff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+  background-color: rgba(0, 0, 0, 0);
+  filter: alpha(opacity=50);
+  opacity: .5;
+}
+.carousel-control.left {
+  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));
+  background-image:         linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+  background-repeat: repeat-x;
+}
+.carousel-control.right {
+  right: 0;
+  left: auto;
+  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));
+  background-image:         linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+  background-repeat: repeat-x;
+}
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #fff;
+  text-decoration: none;
+  filter: alpha(opacity=90);
+  outline: 0;
+  opacity: .9;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+  position: absolute;
+  top: 50%;
+  z-index: 5;
+  display: inline-block;
+  margin-top: -10px;
+}
+.carousel-control .icon-prev,
+.carousel-control .glyphicon-chevron-left {
+  left: 50%;
+  margin-left: -10px;
+}
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-right {
+  right: 50%;
+  margin-right: -10px;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+  width: 20px;
+  height: 20px;
+  font-family: serif;
+  line-height: 1;
+}
+.carousel-control .icon-prev:before {
+  content: '\2039';
+}
+.carousel-control .icon-next:before {
+  content: '\203a';
+}
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 60%;
+  padding-left: 0;
+  margin-left: -30%;
+  text-align: center;
+  list-style: none;
+}
+.carousel-indicators li {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin: 1px;
+  text-indent: -999px;
+  cursor: pointer;
+  background-color: #000 \9;
+  background-color: rgba(0, 0, 0, 0);
+  border: 1px solid #fff;
+  border-radius: 10px;
+}
+.carousel-indicators .active {
+  width: 12px;
+  height: 12px;
+  margin: 0;
+  background-color: #fff;
+}
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #fff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+}
+.carousel-caption .btn {
+  text-shadow: none;
+}
+@media screen and (min-width: 768px) {
+  .carousel-control .glyphicon-chevron-left,
+  .carousel-control .glyphicon-chevron-right,
+  .carousel-control .icon-prev,
+  .carousel-control .icon-next {
+    width: 30px;
+    height: 30px;
+    margin-top: -10px;
+    font-size: 30px;
+  }
+  .carousel-control .glyphicon-chevron-left,
+  .carousel-control .icon-prev {
+    margin-left: -10px;
+  }
+  .carousel-control .glyphicon-chevron-right,
+  .carousel-control .icon-next {
+    margin-right: -10px;
+  }
+  .carousel-caption {
+    right: 20%;
+    left: 20%;
+    padding-bottom: 30px;
+  }
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
+.clearfix:before,
+.clearfix:after,
+.dl-horizontal dd:before,
+.dl-horizontal dd:after,
+.container:before,
+.container:after,
+.container-fluid:before,
+.container-fluid:after,
+.row:before,
+.row:after,
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after,
+.btn-toolbar:before,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after,
+.nav:before,
+.nav:after,
+.navbar:before,
+.navbar:after,
+.navbar-header:before,
+.navbar-header:after,
+.navbar-collapse:before,
+.navbar-collapse:after,
+.pager:before,
+.pager:after,
+.panel-body:before,
+.panel-body:after,
+.modal-header:before,
+.modal-header:after,
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+.clearfix:after,
+.dl-horizontal dd:after,
+.container:after,
+.container-fluid:after,
+.row:after,
+.form-horizontal .form-group:after,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:after,
+.nav:after,
+.navbar:after,
+.navbar-header:after,
+.navbar-collapse:after,
+.pager:after,
+.panel-body:after,
+.modal-header:after,
+.modal-footer:after {
+  clear: both;
+}
+.center-block {
+  display: block;
+  margin-right: auto;
+  margin-left: auto;
+}
+.pull-right {
+  float: right !important;
+}
+.pull-left {
+  float: left !important;
+}
+.hide {
+  display: none !important;
+}
+.show {
+  display: block !important;
+}
+.invisible {
+  visibility: hidden;
+}
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+.hidden {
+  display: none !important;
+}
+.affix {
+  position: fixed;
+}
+@-ms-viewport {
+  width: device-width;
+}
+.visible-xs,
+.visible-sm,
+.visible-md,
+.visible-lg {
+  display: none !important;
+}
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block {
+  display: none !important;
+}
+@media (max-width: 767px) {
+  .visible-xs {
+    display: block !important;
+  }
+  table.visible-xs {
+    display: table !important;
+  }
+  tr.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-xs,
+  td.visible-xs {
+    display: table-cell !important;
+  }
+}
+@media (max-width: 767px) {
+  .visible-xs-block {
+    display: block !important;
+  }
+}
+@media (max-width: 767px) {
+  .visible-xs-inline {
+    display: inline !important;
+  }
+}
+@media (max-width: 767px) {
+  .visible-xs-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: block !important;
+  }
+  table.visible-sm {
+    display: table !important;
+  }
+  tr.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm-block {
+    display: block !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm-inline {
+    display: inline !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md {
+    display: block !important;
+  }
+  table.visible-md {
+    display: table !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md-block {
+    display: block !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md-inline {
+    display: inline !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg {
+    display: block !important;
+  }
+  table.visible-lg {
+    display: table !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg-block {
+    display: block !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg-inline {
+    display: inline !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (max-width: 767px) {
+  .hidden-xs {
+    display: none !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-sm {
+    display: none !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-md {
+    display: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .hidden-lg {
+    display: none !important;
+  }
+}
+.visible-print {
+  display: none !important;
+}
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  table.visible-print {
+    display: table !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+}
+.visible-print-block {
+  display: none !important;
+}
+@media print {
+  .visible-print-block {
+    display: block !important;
+  }
+}
+.visible-print-inline {
+  display: none !important;
+}
+@media print {
+  .visible-print-inline {
+    display: inline !important;
+  }
+}
+.visible-print-inline-block {
+  display: none !important;
+}
+@media print {
+  .visible-print-inline-block {
+    display: inline-block !important;
+  }
+}
+@media print {
+  .hidden-print {
+    display: none !important;
+  }
+}
+/*# sourceMappingURL=bootstrap.css.map */
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/project_profile.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/project_profile.css
new file mode 100644 (file)
index 0000000..03527f0
--- /dev/null
@@ -0,0 +1,12 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+.container-custom {
+  max-width: 100% !important;
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/search_form.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/search_form.css
new file mode 100644 (file)
index 0000000..4598cff
--- /dev/null
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+input.search-input-rest
+{
+  font-weight: 400;
+  margin: 0px 0;
+  height: 30px;
+  padding: 10px 30px;
+    min-width: 90%;
+    max-width: 90%;
+  /*max-width: 500px;
+  */
+  border-radius: 5px;
+  border: 2px solid #333333;
+  box-shadow: 0 0 15px 1px rgba(0,0,0,0.50);
+  color: #333333;
+  font-size: 22px;
+}
+.material-search-custom {
+  left : -30px;
+
+  padding-left: 10px;
+}
+.form-group-custom {
+  width : 400px;
+  position: relative;
+  float: right;
+  left: -40%;
+}
+.form-control-custom {
+  padding-top: 10px;
+  padding-left: 50px;
+}
+input[type="search"]:focus:not([readonly]) {
+  transition: all 0s !important;
+  border-radius: 5px;
+  border: 2px solid #333333;
+  box-shadow: 0 0 15px 1px rgba(0,0,0,0.50);
+  color: #333333;
+}
+.gray {
+  background: rgb(249,249,249);
+}
+span.glyphicon.glyphicon-search.form-control-feedback,
+    div.form-group-custom i.material-icons {
+      top: 0.5em;
+      left: 16.0em;
+      cursor:pointer;
+      z-index: 30;
+      position: absolute;
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/search_projects.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/search_projects.css
new file mode 100644 (file)
index 0000000..c000711
--- /dev/null
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+.card-shadow-custom {
+  box-shadow: 0 2px 3px 0 rgba(0,0,0,0.50);
+  border-bottom: 2px solid #8B19A2;
+}
+.row-custom {
+  display: flex;
+  flex-flow: row wrap;
+  width: 100%;
+  margin: 1em 1em 1em 1em;
+  padding-right: 20px;
+}
+.card-title-div-custom {
+  margin: 0.5em 0 0.5em 0;
+  text-align: left;
+  display: inline-block;
+}
+.card-title-span-custom {
+  margin: 0 0 0 2em;
+  display: inline-block;
+}
+.card-title-div-custom-right {
+  margin: 0.5em 0em 0.5em 0;
+  text-align: right;
+  display: inline-block;
+}
+.card-image-picture-custom {
+  margin: auto;
+}
+.collection .collection-item.active {
+  background-color: rgb(255,245,114);
+  text-decoration: none;
+  transition: none;
+}
+.collection a.collection-item:not(.active):hover {
+  background-color: rgb(255,245,114);
+  text-decoration: none;
+  transition: none;
+}
+.collection .collection-item {
+  padding: 1px 25px;
+  border-bottom: 0px;
+  transition: none;
+}
+.chip > a {
+  display: block;
+  text-decoration: none;
+  text-align: center;
+  display:inline-block;
+    font-size:13px;
+    font-weight:500;
+    color:rgba(0, 0, 0, 0.6) !important;
+    margin-right: 15px !important;
+    margin-left: 15px !important;
+    text-transform: none !important;
+}
+.chip > a:hover {
+  background-color: transparent;
+  text-decoration: none;
+  transition: none;
+}
+a.a-custom-more {
+  display: block;
+  text-decoration: none;
+  text-align: center;
+  display:inline-block;
+    font-size: 20px;
+    font-weight:500;
+    color:rgba(0, 0, 0, 0.6) !important;
+    margin-right: 15px !important;
+    text-transform: none !important;
+}
+a.a-custom-more:hover {
+  background-color: transparent;
+}
+.collection-custom {
+  margin: 0em 0em 0em 1em;
+}
+.card .row .card-action-custom {
+  margin-left: 20px;
+}
+.rating {
+  color: rgb(253,225,109) !important;
+}
+.filled-stars .star i {
+  color: rgb(253,225,109) !important;
+}
+.glyphicon-star-empty {
+  color: rgb(221,221,221);
+}
+.star {
+}
+span.glyphicon.glyphicon-search.form-control-feedback,
+    div.form-group-custom i.material-icons {
+      top: 0.5em;
+      left: 16.0em;
+      cursor:pointer;
+      z-index: 30;
+      position: absolute;
+}
+.card-image-custom {
+  display: flex;
+  flex-flow: column wrap;
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/style.css b/utils/test/vnfcatalogue/VNF_Catalogue/public/stylesheets/style.css
new file mode 100644 (file)
index 0000000..f5355ba
--- /dev/null
@@ -0,0 +1,301 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+@import url('https://fonts.googleapis.com/css?family=Muli:300,400,600,700,800');
+*
+{
+  color: #333333;
+  font-family: 'Muli', sans-serif;
+}
+*:focus
+{
+    outline: none;
+}
+html,
+body
+{
+  margin: 0;
+  padding: 0;
+  background: #ffffff;
+  font-family: 'Muli', sans-serif;
+}
+header
+{
+  padding: 10px 35px 0 0px;
+}
+header ul
+{
+  list-style: none;
+  display: inline-block;
+}
+header ul li
+{
+  display: inline-block;
+}
+header .logo
+{
+  background: url(../images/logo.png) no-repeat;
+  background-size: cover;
+
+}
+header .brand-logo-extends
+{
+  background: url(../images/logo.png) no-repeat;
+  background-size: cover;
+  width: 154px;
+  height: 34px;
+  margin-top: 11%;
+  margin-right: 0%;
+  padding: 0;
+  position: relative !important;
+  /*margin-right: 20px;
+  margin-left: 0;
+  float: left;*/
+  /*display: inline-block;
+  */
+}
+
+nav
+{
+  height: 10px;
+}
+
+header ul li.links
+{
+  margin: 7px 10px 0 0;
+}
+header ul li > a,
+.content ul.most-menu li.items a
+{
+  color: #333333;
+  font-weight: 800;
+  font-size: 14px;
+  letter-spacing: 0.6px;
+  font-family: 'Muli', sans-serif;
+}
+header ul.navigation-right
+{
+  float: right;
+  padding-top: 8px;
+}
+header li.signup > a
+{
+  border: 2px solid #333333;
+  border-radius: 4px;
+  font-size: 14px;
+  font-weigt: 700;
+  padding: 2px 10px;
+}
+header li.signin > a
+{
+  border-bottom: 2px solid #333333;
+  font-size: 13px;
+  font-weight: 700;
+  padding: 0px 2px;
+}
+header li.option
+{
+  font-weight: 800;
+  padding: 0 10px;
+}
+header ul li > a:hover,
+header li.signin > a:hover,
+header li.signup > a:hover,
+header ul li > a:focus,
+header li.signin > a:focus,
+header li.signup > a:focus,
+.content ul.most-menu li a:hover,
+.content ul.most-menu li a:focus
+{
+  text-decoration: none;
+  cursor: pointer;
+  color: #333333;
+}
+header li.signup > a:hover
+{
+  background: #333333;
+  color: #ffffff;
+}
+.search-box
+{
+  text-align: center;
+  padding: 100px 0;
+}
+.search-box h1
+{
+  font-size: 30px;
+  letter-spacing: 2px;
+  color: #333333;
+  font-weight: 600;
+}
+form.search-form
+{
+  padding: 10px 20px;
+}
+form.search-form input.search-input
+{
+  font-weight: 400;
+  margin: 30px 0;
+  height: 80px;
+  padding: 10px 30px;
+  max-width: 800px;
+  width: 70%;
+  border-radius: 5px;
+  border: 2px solid #333333;
+  box-shadow: 0 0 15px 1px rgba(0,0,0,0.50);
+  color: #333333;
+  font-size: 22px;
+}
+
+form.search-form button.search-button
+{
+  padding: 18px 35px;
+  background: #FFF572;
+  border: 0;
+  box-shadow: 0 0 15px 1px #958F40;
+  border-radius: 1px;
+  font-size: 20px;
+  color: #393E41;
+  letter-spacing: 1px;
+  border-radius: 5px;
+  font-weight: 600;
+}
+form.search-form input:focus
+{
+  outline: none;
+}
+form.search-form input::-webkit-input-placeholder
+{
+  font-weight: 400;
+  letter-spacing: 1px;
+    color: #333333;
+}
+form.search-form input::-moz-placeholder
+{
+  font-weight: 400;
+  letter-spacing: 1px;
+    color: #333333;
+}
+form.search-form input:-moz-placeholder
+{
+  font-weight: 400;
+  letter-spacing: 5px;
+    color: #333333;
+}
+form.search-form input:-ms-input-placeholder
+{
+  font-weight: 400;
+  letter-spacing: 1px;
+    color: #333333;
+}
+.content
+{
+  height: 500px;
+  background: #f9f9f9;
+  padding: 10px 0;
+}
+.content ul.most-menu
+{
+  list-style: none;
+  text-align: center;
+  padding-bottom: 10px;
+}
+.content ul.most-menu li.items
+{
+  display: inline-block;
+  margin-right: 5px;
+  padding: 15px 25px;
+}
+.content ul.most-menu li.active
+{
+  /*background: #FFF572;*/
+}
+.content-box
+{
+  overflow: hidden;
+  padding: 20px 0 50px 0;
+  display: flex;
+  justify-content: center;
+  background: #FFFFFF;
+  box-shadow: 0 2px 3px 0 rgba(0,0,0,0.50);
+  border-bottom: 2px solid #8B19A2;
+  margin-bottom: 30px;
+}
+.content-data
+{
+  align-self: center;
+}
+.content-data h1.content-title
+{
+  font-size: 25px;
+  color: #000000;
+  letter-spacing: 1.2px;
+}
+.content-data .box
+{
+  padding: 10px 0;
+  height: 90px;
+  text-align: center;
+  border: 2px solid #4D4D4D;
+  border-radius: 2px;
+}
+.content-data .commit-icon
+{
+  width: 23px;
+  height: 16px;
+}
+.content-data .box h3.commits
+{
+  text-align: center;
+  font-size: 12px;
+  color: #333333;
+  letter-spacing: 0.03px;
+}
+.content-height-overwrite
+{
+  height: 110px;
+}
+.float-center-magic
+{
+  float: right;
+  position: relative;
+  left: -30%;
+}
+nav ul li:hover, nav ul li.active, nav ul li a.active, nav ul li a:hover {
+  background-color: rgb(255,245,114);
+}
+a:hover, a.active {
+  background-color: rgb(255,245,114);
+}
+footer
+{
+  font-size: 12px;
+  font-weight: 800;
+  color: #333333;
+  text-align: center;
+  padding: 20px;
+}
+.space-10
+{
+  height: 10px;
+}
+.space-30
+{
+  height: 100px;
+}
+input[type="search"]:focus:not([readonly]) {
+  transition: all 0s !important;
+  border-radius: 5px;
+  border: 2px solid #333333;
+  box-shadow: 0 0 15px 1px rgba(0,0,0,0.50);
+  color: #333333;
+}
+.gray {
+  background: rgb(249,249,249);
+}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/public/uploads/logo.png b/utils/test/vnfcatalogue/VNF_Catalogue/public/uploads/logo.png
new file mode 100644 (file)
index 0000000..fe18194
Binary files /dev/null and b/utils/test/vnfcatalogue/VNF_Catalogue/public/uploads/logo.png differ
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/add_project.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/add_project.js
new file mode 100644 (file)
index 0000000..229620d
--- /dev/null
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+var multer = require('multer');
+
+
+var storage =   multer.diskStorage({
+  destination: function (req, file, callback) {
+    callback(null, './public/uploads');
+  },
+  filename: function (req, file, callback) {
+    console.log(file);
+    console.log(req.body);
+    callback(null, file.fieldname + '-' + Date.now() + '.jpg');
+  }
+});
+
+var fileFilter = function (req, file, cb) {
+  if (file.mimetype !== 'image/png') {
+    //req.fileValidationError = 'goes wrong on the mimetype';
+    cb(null, false);
+  } else {
+    cb(null, true);
+  }
+}
+
+var upload = multer({ fileFilter: fileFilter, storage : storage}).single('file_upload');
+
+
+router.post('/', function(req, res) {
+  upload(req,res,function(err) {
+        console.log(req.body);
+        console.log(req.file)
+        if(req.file == null && req.body['file_url'] != '') {
+            response = 'File Upload error: wrong Filetype';
+            res.status(500);
+            res.end(JSON.stringify({'error': response}));
+
+        }
+        if(err) {
+            console.log(err);
+            response = 'File Upload error: ' + err;
+            console.log(response);
+            //return res.end(req.fileValidationError);
+            res.status(500);
+            res.send({'error': response});
+            return;
+        }
+
+        console.log(req.file);
+        req.body['photo_url'] = (req.file) ? req.file['filename'] : 'logo.png';
+        console.log(req.body);
+
+        req.checkBody("vnf_name", "VNF Name must not be empty").notEmpty();
+        req.checkBody("repo_url", "Repository URL must not be empty").notEmpty();
+        req.checkBody("license", "Please select a License").notEmpty();
+        req.checkBody("opnfv_indicator", "Please select an OPNFV Indicator").notEmpty();
+        req.checkBody("repo_url", "Must be a Github URL").matches('.*github\.com.*');
+
+        var errors = req.validationErrors();
+        console.log(errors);
+
+        var response = '';  for(var i = 0; i < errors.length; i++) {
+            console.log(errors[i]['msg']);
+            response = response + errors[i]['msg'] + '; ';
+        }
+
+        if(errors) {    res.status(500);
+            res.send({'error': response});
+            return;
+        }
+
+        var vnf_details = req.body;
+        delete vnf_details.file_url;
+
+        db_pool.getConnection(function(err, connection) {
+            // Use the connection
+
+          sql_query = 'INSERT INTO photo(photo_url) values(\'' + req.body['photo_url'] + '\')\;SELECT LAST_INSERT_ID() photo_id';
+          // TODO look above query prone to sql_injections
+
+          console.log(sql_query);
+          connection.query(sql_query, function (error, results, fields) {
+             console.log('hola');
+             console.log(results[1][0].photo_id);
+              //connection.query(sql_query, vnf_details, function (error, results, fields) {
+             delete vnf_details.photo_url;
+             vnf_details['photo_id'] = results[1][0].photo_id;
+             sql_query = 'INSERT INTO vnf SET ?'
+               connection.query(sql_query, vnf_details, function (error, results, fields) {
+             // And done with the connection.
+             connection.release();
+             if (error) throw error;
+
+             // Handle error after the release.
+             res.end('{"success" : "Updated Successfully", "status" : 200}');
+             return;
+               // Don't use the connection here, it has been returned to the pool.
+               });
+          });
+        });
+
+
+  });
+
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/add_tag.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/add_tag.js
new file mode 100644 (file)
index 0000000..511f4cc
--- /dev/null
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+
+router.post('/', function(req, res) {
+  console.log(req.body);
+  req.checkBody("tag_name", "TAG Name must not be empty").notEmpty();
+
+  var errors = req.validationErrors();
+  console.log(errors);
+
+  var response = '';  for(var i = 0; i < errors.length; i++) {
+    console.log(errors[i]['msg']);
+    response = response + errors[i]['msg'] + '; ';
+  }
+
+  if(errors) {  res.status(500);
+    res.send({'error': response});
+    return;
+  }
+
+  var tag_details = req.body;
+
+  db_pool.getConnection(function(err, connection) {
+    // Use the connection
+    sql_query = 'INSERT INTO tag SET ?'
+    connection.query(sql_query, tag_details, function (error, results, fields) {
+        // And done with the connection.
+      res.end('{"success" : "Updated Successfully", "status" : 200}');
+      return;
+        connection.release();
+        // Handle error after the release.
+        if (error) throw error;
+        // Don't use the connection here, it has been returned to the pool.
+    });
+  });
+
+
+  res.end('{"success" : "Updated Successfully", "status" : 200}');
+  return;
+
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/index.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/index.js
new file mode 100644 (file)
index 0000000..950fcd5
--- /dev/null
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+
+/* GET VNF_Catalogue Home Page. */
+router.get('/', function(req, res) {
+  res.render('index', { title: 'Express' });
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/project_profile.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/project_profile.js
new file mode 100644 (file)
index 0000000..be06642
--- /dev/null
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+
+router.get('/', function(req, res) {
+  var tags = req.param('tags');
+  console.log(tags);
+  res.render('project_profile', { title: 'Express' });
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/search_projects.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/search_projects.js
new file mode 100644 (file)
index 0000000..96f68db
--- /dev/null
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+var async = require('async');
+
+
+var renderer = function(res, err, results) {
+    console.log(results);
+    res.render('search_projects', { title: 'Express', json: results });
+}
+
+var get_tags = function(result, callback) {
+    db_pool.getConnection(function(err, connection) {
+        sql_query = 'select tag_name from tag where tag_id in (select tag_id from vnf_tags where vnf_id = ' + result['vnf_id'] + ') limit 5';
+        // TODO find why it works and not above
+        connection.query(sql_query, function (error, results, fields) {
+            console.log(results);
+            result['tags'] = results;
+            callback(null, result);
+            //connection.release();
+            if (error) throw error;
+        });
+    });
+}
+
+
+var get_images = function(result, callback) {
+    db_pool.getConnection(function(err, connection) {
+        sql_query = 'select photo_url from photo where photo_id = ' + result['photo_id'];
+        // TODO find why it works here and not when declared outside the method
+        console.log(sql_query);
+        connection.query(sql_query, function (error, results, fields) {
+            console.log(results[0].photo_url);
+            result['photo_url'] = results[0].photo_url;
+            callback(null, result);
+            //connection.release();
+            if (error) throw error;
+        });
+    });
+}
+
+var sql_data = function(tags, renderer, res) {
+    var tag_array = "\'" + tags.map(function (item) { return item; }).join("\',\'") + "\'";
+    console.log(tag_array);
+    var condition = '';
+    db_pool.getConnection(function(err, connection) {
+        sql_query = 'select tag_id from tag where tag_name in (' + tag_array + ')';
+        connection.query(sql_query, function (error, results, fields) {
+            condition = 'SELECT * FROM vnf as v';
+            for (var i in results) {
+                condition += (i == 0) ? ' WHERE ' : ' AND ';
+                condition += 'v.vnf_id IN (SELECT vnf_id from vnf_tags where tag_id = ' + results[i]['tag_id'] + ')';
+            }
+
+            connection.query(condition, function (error, results, fields) {
+                    console.log(results);
+                    async.map(results, get_images, function(error, results) {
+                        async.map(results, get_tags, renderer.bind(null, res));
+                    });
+                    //connection.release();
+                    if (error) throw error;
+            });
+
+            connection.release();
+            if (error) throw error;
+        });
+    });
+
+}
+
+router.get('/', function(req, res) {
+
+  console.log(typeof(req.param('tags')));
+  var tags = req.param('tags');
+
+  if(tags) {
+    tags = tags.toLowerCase().split(/[ ,]+/);
+    console.log(tags);
+    sql_data(tags, renderer, res);
+  } else {
+    res.render('search_projects', { title: 'Express', json: false});
+  }
+
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/search_tag.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/search_tag.js
new file mode 100644 (file)
index 0000000..cbe8cae
--- /dev/null
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+
+/* Post Controller for Tag autocomplete form */
+router.get('/', function(req, res) {
+    tag_partial = req.param('key');
+    db_pool.getConnection(function(err, connection) {
+
+        sql_query = 'select tag_name from tag where tag_name like "%'+ tag_partial + '%" limit 5';
+        // TODO find why it works and not above
+        connection.query(sql_query, function (error, results, fields) {
+            console.log(results);
+
+            var data=[];
+            for(i = 0; i < results.length; i++) {
+                data.push(results[i].tag_name.replace(/\r?\n|\r/g, ''));
+            }
+            console.log(results);
+            connection.release();
+            res.end(JSON.stringify(results));
+
+            if (error) throw error;
+        });
+    });
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/search_vnf.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/search_vnf.js
new file mode 100644 (file)
index 0000000..a5cf09c
--- /dev/null
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+
+/* Post Controller for Search Vnf autocomplete form */
+router.get('/', function(req, res) {
+    tag_partial = req.param('key');
+    db_pool.getConnection(function(err, connection) {
+
+        sql_query = 'select vnf_name from vnf where vnf_name like "%'+ tag_partial + '%" limit 5';
+        // TODO find why it works and not above
+        connection.query(sql_query, function (error, results, fields) {
+            console.log(results);
+
+            var data=[];
+            for(i = 0; i < results.length; i++) {
+                data.push(results[i].vnf_name.replace(/\r?\n|\r/g, ''));
+            }
+            console.log(results);
+            connection.release();
+            res.end(JSON.stringify(results));
+
+            if (error) throw error;
+        });
+    });
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/routes/vnf_tag_association.js b/utils/test/vnfcatalogue/VNF_Catalogue/routes/vnf_tag_association.js
new file mode 100644 (file)
index 0000000..d1a3d72
--- /dev/null
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var express = require('express');
+var router = express.Router();
+
+/* Post controller for VNF_TAG Association */
+router.post('/', function(req, res) {
+  req.checkBody("tag_name", "TAG Name must not be empty").notEmpty();
+  req.checkBody("vnf_name", "VNF Name must not be empty").notEmpty();
+
+  var errors = req.validationErrors();
+  console.log(errors);
+
+  var response = '';  for(var i = 0; i < errors.length; i++) {
+    console.log(errors[i]['msg']);
+    response = response + errors[i]['msg'] + '; ';
+  }
+
+  if(errors) {  res.status(500);
+    res.send({'error': response});
+    return;
+  }
+
+  var tag_name = req.param('tag_name');
+  var vnf_name = req.param('vnf_name');
+
+  db_pool.getConnection(function(err, connection) {
+    // Use the connection
+    //sql_query = 'INSERT INTO tag SET ?'
+    sql_query = 'insert into vnf_tags(vnf_id, tag_id) values ((select vnf_id from vnf where vnf_name = \'' + vnf_name + '\'), (select tag_id from tag where tag_name = \'' + tag_name + '\'))';
+    console.log(sql_query);
+    connection.query(sql_query, function (error, results, fields) {
+        // And done with the connection.
+
+        connection.release();
+        res.end('{"success" : "Updated Successfully", "status" : 200}');
+
+        // Handle error after the release.
+        if (error) throw error;
+        // Don't use the connection here, it has been returned to the pool.
+    });
+  });
+
+  res.end('{"success" : "Updated Successfully", "status" : 200}');
+  //res.render('vnf_tag_association', { title: 'Express' });
+});
+
+module.exports = router;
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/add_project.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/add_project.jade
new file mode 100644 (file)
index 0000000..dde8cfe
--- /dev/null
@@ -0,0 +1,164 @@
+
+.search-box
+  script(src='/3rd_party/typeahead.js')
+  script(type='text/javascript', src='/javascripts/mode_edit.js')
+  .fixed-action-btn.fixed-action-btn_custom
+    a.btn-floating.btn-large.red
+      i.large.material-icons mode_edit
+    ul
+      li
+        a.btn-floating.red.tooltipped(href='#modal2', data-position='left', data-delay='50', data-tooltip='Add a TAG')
+          i.large.material-icons attach_file
+      li
+        a.btn-floating.green.tooltipped(href='#modal1', data-position='left', data-delay='50', data-tooltip='Add a VNF')
+          i.large.material-icons add
+      li
+        a.btn-floating.blue.tooltipped(href='#modal3', data-position='left', data-delay='50', data-tooltip='Add a TAG to a VNF')
+          i.large.material-icons share
+  #modal1.modal
+    .modal-content
+      h4.center
+        i.material-icons library_add
+        |         Add a VNF
+      .row
+        form#add_project_form.col.s12(action='/add_project', enctype='multipart/form-data', method='post')
+          .row.modal-form-row
+            .input-field.col.s12
+              input#vnf_name.validate(type='text', name='vnf_name')
+              label.left-align(for='vnf_name') Name
+          .row
+            .input-field.col.s12
+              input#repo_url.validate(type='text', name='repo_url')
+              label.left-align(for='repo_url') Github URL
+          .row
+            .input-field.col.s12
+              select#license
+                option(value='', name='license', disabled='', selected='') Choose the License
+                option(value='MIT') MIT
+                option(value='GPL') GPL
+                option(value='GPL_V2') GPL_V2
+                option(value='BSD') BSD
+                option(value='APACHE') APACHE
+              label License
+          .row
+            .input-field.col.s12
+              select#opnfv_indicator
+                option(value='', name='opnfv_indicator', disabled='', selected='') Choose the OPNFV Indicator
+                option(value='silver') silver
+                option(value='gold') gold
+                option(value='platinum') platinum
+              label OPNFV Indicator
+
+          .row
+            .file-field.input-field
+              .btn
+                span Photo (Optional)
+                input#file_upload(type='file', name='file_upload')
+              .file-path-wrapper
+                input.file-path.validate(type='text', name='file_url')
+
+          .row
+            .input-field.col.s12
+              input#submitter_id.validate(type='hidden', name='submitter_id', value=1)
+
+          .row
+            button#add_project_button.modal-action.modal-close.waves-effect.waves-light.btn.right
+              | Submit VNF
+              i.material-icons.right send
+  #modal2.modal
+    .modal-content
+      h4.center
+        i.material-icons library_add
+        |         Add a TAG
+      .row
+        form#add_tag_form.col.s12(action='/add_tag', method='post')
+          .row.modal-form-row
+            .input-field.col.s12
+              input#tag_name.validate(type='text', name='tag_name')
+              label.left-align(for='tag_name') Name
+          button#add_tag_button.modal-action.modal-close.waves-effect.waves-light.btn.right
+            | Submit TAG
+            i.material-icons.right send
+  #modal3.modal
+      h4.center
+        i.material-icons library_add
+        |         Add a TAG to a VNF
+      .row
+        form#add_vnf_tag_association_form.col.s12(action='/vnf_tag_association', method='post')
+
+          .row.modal-form-row.modal-form-row-custom
+            .input-field.col.s2 VNF Name
+            #scrollable-dropdown-menu.input-field.col.s4
+              input#vnf_name.typeahead(type='text', name='vnf_name')
+              //
+                label.left-align(for='tag_name') VNF Name
+            .input-field.col.s2 TAG Name
+            #scrollable-dropdown-menu.input-field.col.s4
+              input#tag_name.validate.typeahead(type='text', name='tag_name')
+              //
+                label.left-align(for='tag_name') TAG Name
+
+          button#add_vnf_tag_association_button.modal-action.modal-close.waves-effect.waves-light.btn.right
+            | Submit
+            i.material-icons.right send
+  style.
+    .select-dropdown{
+        overflow-y: auto !important;
+    }
+    .dropdown-content {
+        max-height: 200px !important;
+    }
+    .backdrop{
+       background-color: rgb(253,225,109);
+    }
+    .bg {
+    }
+    .modal-form-row-custom {
+      min-height: 200px !important;
+    }
+    #scrollable-dropdown-menu .tt-menu {
+      max-height: 150px;
+      overflow-y: auto;
+    }
+
+    .typeahead, .tt-query, .tt-hint {
+        border: 2px solid #CCCCCC;
+        border-radius: 8px 8px 8px 8px;
+        font-size: 24px;
+        height: 30px;
+        line-height: 30px;
+        outline: medium none;
+        padding: 8px 12px;
+        width: 396px;
+    }
+
+    .tt-query {
+        box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset;
+    }
+    .tt-hint {
+        color: #999999;
+    }
+    .tt-dropdown-menu {
+        background-color: #FFFFFF;
+        border: 1px solid rgba(0, 0, 0, 0.2);
+        border-radius: 8px 8px 8px 8px;
+        box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+        margin-top: 12px;
+        padding: 8px 0;
+        width: 200px;
+    }
+    .tt-suggestion {
+        font-size: 18px;
+        line-height: 24px;
+        padding: 3px 20px;
+    }
+    .tt-suggestion.tt-cursor {
+        background-color: #0097CF;
+        color: #FFFFFF;
+    }
+    .tt-suggestion p {
+        margin: 0;
+    }
+    .tt-dropdown-menu, .gist {
+        text-align: left;
+    }
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/error.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/error.jade
new file mode 100644 (file)
index 0000000..4f7fbca
--- /dev/null
@@ -0,0 +1,12 @@
+// 
+  Copyright (c) 2017 Kumar Rishabh and others.
+  All rights reserved. This program and the accompanying materials
+  are made available under the terms of the Apache License, Version 2.0
+  which accompanies this distribution, and is available at
+  http://www.apache.org/licenses/LICENSE-2.0
+extends layout
+
+block content
+  h1= message
+  h2= error.status
+  pre #{error.stack}
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/index.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/index.jade
new file mode 100644 (file)
index 0000000..bf0cd14
--- /dev/null
@@ -0,0 +1,123 @@
+extends layout
+
+//
+  Copyright (c) 2017 Kumar Rishabh and others.
+  All rights reserved. This program and the accompanying materials
+  are made available under the terms of the Apache License, Version 2.0
+  which accompanies this distribution, and is available at
+  http://www.apache.org/licenses/LICENSE-2.0
+block content
+  link(rel='stylesheet', href='/stylesheets/3rd_party/bootstrap.css')
+  .search-box
+    h1 VNF Catalogue
+    form.search-form
+      input.search-input(type='search', placeholder='Search...', id='Tags')
+      .space-10
+      button.search-button(type='submit', value='Search', id='Search') Search
+  .content.content-height-overwrite
+    nav.z-depth-0.transparent
+      .nav-wrapper
+        ul#nav-mobile.float-center-magic.hide-on-med-and-down.most-menu
+          li.items
+            a(href='#') Most Popular
+          li.items
+            a(href='#') Most Active
+          li.items
+            a(href='#') Most Active Contributions
+  .content
+    .container
+      .row
+        .box-container
+          .col-md-3
+            .content-box
+              .content-data
+                h1.content-title Beacon
+                .box
+                  img.commit-icon(src='/images/3rd_party/commits.png')
+                  h3.commits
+                    | 4,845
+                    br
+                    | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+        .col-md-3
+          .content-box
+            .content-data
+              h1.content-title Beacon
+              .box
+                img.commit-icon(src='/images/3rd_party/commits.png')
+                h3.commits
+                  | 4,845
+                  br
+                  | commits
+    footer
+      | © 2017 XYZ Company
+  style(type='text/css').
+    input[type="text"]:focus:not([readonly]) {
+      transition: all 0s !important;
+      border-radius: 5px;
+      border: 2px solid #333333;
+      box-shadow: 0 0 15px 1px rgba(0,0,0,0.50);
+      color: #333333;
+    }
+    .gray {
+      background: rgb(249,249,249);
+    }
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/layout.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/layout.jade
new file mode 100644 (file)
index 0000000..33c09e3
--- /dev/null
@@ -0,0 +1,54 @@
+doctype html
+//
+  Copyright (c) 2017 Kumar Rishabh and others.
+  All rights reserved. This program and the accompanying materials
+  are made available under the terms of the Apache License, Version 2.0
+  which accompanies this distribution, and is available at
+  http://www.apache.org/licenses/LICENSE-2.0
+html(lang='en')
+html
+    head
+        title= title
+        link(rel='stylesheet', href='/3rd_party/materialize/css/materialize.css')
+        script(type='text/javascript', src='https://code.jquery.com/jquery-3.1.1.min.js')
+        script(src='/javascripts/global.js')
+        script(src='/3rd_party/materialize/js/materialize.js')
+        link(href='https://fonts.googleapis.com/icon?family=Material+Icons', rel='stylesheet')
+        link(rel='stylesheet', href='/stylesheets/style.css')
+    body
+        header
+          nav.transparent.z-depth-0
+            .nav-wrapper
+              a.button-collapse(href='#', data-activates='mobile-demo')
+                i.material-icons menu
+              ul#nav-mobile.left.hide-on-med-and-down
+                li
+                  a(href='#')
+                    img.left.brand-logo.brand-logo-extends
+                li
+                  a(href='#') Projects
+                li
+                  a(href='#') People
+                li
+                  a(href='#') About
+              ul#nav-mobile.right.hide-on-med-and-down
+                li.signup
+                  a(href='#') Sign up
+                li.option or
+                li.signin
+                  a(href='#') Sign in
+              ul#mobile-demo.side-nav
+                li
+                  a(href='#') Projects
+                li
+                  a(href='#') People
+                li
+                  a(href='#') About
+                li.signup
+                  a(href='#') Sign up
+                li.signin
+                  a(href='#') Sign in
+        block search
+        block add_project
+        block content
+
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/project_profile.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/project_profile.jade
new file mode 100644 (file)
index 0000000..7b37bd4
--- /dev/null
@@ -0,0 +1,84 @@
+extends layout
+
+//
+  Copyright (c) 2017 Kumar Rishabh and others.
+  All rights reserved. This program and the accompanying materials
+  are made available under the terms of the Apache License, Version 2.0
+  which accompanies this distribution, and is available at
+  http://www.apache.org/licenses/LICENSE-2.0
+block search
+  include search
+block content
+  .content
+    .container.container-custom
+          .carousel
+            a.carousel-item(href='#one!')
+              img(src='http://lorempixel.com/250/250/nature/1')
+            a.carousel-item(href='#two!')
+              img(src='http://lorempixel.com/250/250/nature/2')
+            a.carousel-item(href='#three!')
+              img(src='http://lorempixel.com/250/250/nature/3')
+            a.carousel-item(href='#four!')
+              img(src='http://lorempixel.com/250/250/nature/4')
+            a.carousel-item(href='#five!')
+              img(src='http://lorempixel.com/250/250/nature/5')
+            .card.card-shadow-custom.horizontal
+            .row.row-custom
+          .col.s5.card-title-div-custom
+            span.card-title.card-title-span-custom Card Title
+          .col.s5.card-title-div-custom
+            i.material-icons grade
+            span.card-title PenguinScore: 42
+          .col.s2.card-title-div-custom-right
+            form(action='#')
+              input#search_result_1(type='checkbox')
+              label(for='search_result_1') Compare
+          //
+            <div class="card-action">
+            <a href="#">This is a link</a>
+            </div>
+          .col.s4.card-image.card-image-custom
+            img.card-image-picture-custom(src='/images/logo.png')
+          .col.s8.card-stacked
+            .card-content
+              p
+                .collection.collection-custom
+                  a.collection-item(href='#!')
+                    span
+                      i.material-icons code
+                      |                             Lines Of Code: 1.03M
+                  a.collection-item(href='#!')
+                    span
+                      i.material-icons code
+                      |                             Lines Of Code: 1.03M
+                  a.collection-item(href='#!')
+                    span
+                      i.material-icons code
+                      |                             Lines Of Code: 1.03M
+                  a.collection-item(href='#!')
+                    span
+                      i.material-icons code
+                      |                             Lines Of Code: 1.03M
+            .card-action
+              | Tags:
+              .chip
+                a.a-custom(href='#!') Tag1
+              .chip
+                a.a-custom(href='#!') Tag2
+              .chip
+                a.a-custom(href='#!') Tag3
+              .chip
+                a.a-custom(href='#!') Tag4
+              .chip
+                a.a-custom(href='#!') Tag5
+              a.a-custom-more(href='#!') more
+          .divider
+          .card-action-custom.col.s12.card-action
+            | License:
+            a(href='#') MIT
+            |                     Complexity:
+            a(href='#') Atomic
+  footer
+    | © 2017 XYZ Company
+    link(rel='stylesheet', href='/stylesheets/search_projects.css')
+
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/search.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/search.jade
new file mode 100644 (file)
index 0000000..77b2488
--- /dev/null
@@ -0,0 +1,5 @@
+.search-box
+    link(rel='stylesheet', href='/stylesheets/search_form.css')
+    .form-group-custom.form-group.has-feedback
+      input.search-input-rest.form-control(type='search', placeholder='Search...', id='Tags')
+      i.material-icons search
\ No newline at end of file
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/search_projects.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/search_projects.jade
new file mode 100644 (file)
index 0000000..ac91aa6
--- /dev/null
@@ -0,0 +1,83 @@
+extends layout
+
+//
+  Copyright (c) 2017 Kumar Rishabh and others.
+  All rights reserved. This program and the accompanying materials
+  are made available under the terms of the Apache License, Version 2.0
+  which accompanies this distribution, and is available at
+  http://www.apache.org/licenses/LICENSE-2.0
+block search
+  include search
+block add_project
+  include add_project
+block content
+  .content
+    each key, index in json
+      .container.container-custom
+        .card.card-shadow-custom.horizontal
+          .row.row-custom
+            .col.s5.card-title-div-custom
+              span.card-title.card-title-span-custom #{key.vnf_name}
+            .col.s5.card-title-div-custom
+              i.material-icons grade
+              span.card-title PenguinScore: 42
+            .col.s2.card-title-div-custom-right
+              form(action='#')
+                input#search_result_1(type='checkbox', name='#{key.vnf_name}')
+                label(for='search_result_1') Compare
+            //
+              <div class="card-action">
+              <a href="#">This is a link</a>
+              </div>
+            .col.s4.card-image.card-image-custom
+              img.card-image-picture-custom(src='/uploads/#{key.photo_url}')
+            .col.s8.card-stacked
+              .card-content
+                p
+                  .collection.collection-custom
+                    a.collection-item(href='#!')
+                      span
+                        i.material-icons code
+                        |                             Lines Of Code: #{key.lines_of_code}
+                    a.collection-item(href='#!')
+                      span
+                        i.material-icons person
+                        |                             Number of Developers: #{key.no_of_developers}
+                    a.collection-item(href='#!')
+                      span
+                        i.material-icons star
+                        |                             Number of Stars: #{key.no_of_stars}
+                    a.collection-item(href='#!')
+                      span
+                        i.material-icons description
+                        |                             Number of Versions: #{key.versions}
+              .card-action
+                | Tags:
+                each tag, index in key.tags
+                  .chip
+                    a.a-custom(href='/search_projects?tags=#{tag.tag_name}') #{tag.tag_name}
+                //
+                  .chip
+                    a.a-custom(href='#!') tag1
+                  .chip
+                    a.a-custom(href='#!') Tag2
+                  .chip
+                    a.a-custom(href='#!') Tag3
+                  .chip
+                    a.a-custom(href='#!') Tag4
+                  .chip
+                    a.a-custom(href='#!') Tag5
+                a.a-custom-more(href='#!') more
+            .divider
+            .card-action-custom.col.s12.card-action
+              | License:
+              a(href='#') #{key.license}
+              | Complexity:
+              a(href='#') Atomic
+              | Activity:
+              a(href='#') Medium
+              | OPNFV Indicator:
+              a(href='#') #{key.opnfv_indicator}
+  footer
+    | © 2017 XYZ Company
+    link(rel='stylesheet', href='/stylesheets/search_projects.css')
diff --git a/utils/test/vnfcatalogue/VNF_Catalogue/views/vnf_tag_association.jade b/utils/test/vnfcatalogue/VNF_Catalogue/views/vnf_tag_association.jade
new file mode 100644 (file)
index 0000000..c2e1160
--- /dev/null
@@ -0,0 +1,167 @@
+doctype html
+//
+  Copyright (c) 2017 Kumar Rishabh and others.
+  All rights reserved. This program and the accompanying materials
+  are made available under the terms of the Apache License, Version 2.0
+  which accompanies this distribution, and is available at
+  http://www.apache.org/licenses/LICENSE-2.0
+html(lang='en')
+html
+  head
+    title= title
+    //
+      link(rel='stylesheet', href='/3rd_party/materialize/css/materialize.css')
+    script(type='text/javascript', src='https://code.jquery.com/jquery-3.1.1.min.js')
+    //
+      script(src='/javascripts/global.js')
+    //
+      script(src='/3rd_party/materialize/js/materialize.js')
+    link(href='https://fonts.googleapis.com/icon?family=Material+Icons', rel='stylesheet')
+    link(rel='stylesheet', href='/stylesheets/style.css')
+    script(src='/3rd_party/typeahead.js')
+    script(src='/javascripts/mode_edit.js')
+  body
+    h4.center
+    i.material-icons library_add
+    |         Add a TAG to a VNF
+    .row
+    form#add_tag_form.col.s12(action='/add_tag', method='post')
+      .row.modal-form-row
+          .input-field.col.s6 VNF Name
+          .input-field.col.s6 TAG Name
+      .row.modal-form-row
+        #scrollable-dropdown-menu.input-field.col.s6
+          input#tag_name.typeahead(type='text', name='tag_name')
+          //
+            label.left-align(for='tag_name') VNF Name
+
+        .input-field.col.s6
+          input#tag_name.validate(type='text', name='vnf_name')
+          //
+            label.left-align(for='tag_name') TAG Name
+      .row.modal-form-row
+          .input-field.col.s6
+          .input-field.col.s6
+      .row.modal-form-row
+      button#add_tag_button.modal-action.modal-close.waves-effect.waves-light.btn.right
+        | Submit
+        i.material-icons.right send
+style.
+  #scrollable-dropdown-menu .tt-menu {
+    max-height: 150px;
+    overflow-y: auto;
+  }
+
+  .typeahead, .tt-query, .tt-hint {
+      border: 2px solid #CCCCCC;
+      border-radius: 8px 8px 8px 8px;
+      font-size: 24px;
+      height: 30px;
+      line-height: 30px;
+      outline: medium none;
+      padding: 8px 12px;
+      width: 396px;
+  }
+  .typeahead {
+      background-color: #FFFFFF;
+  }
+  .typeahead:focus {
+      border: 2px solid #0097CF;
+  }
+  .tt-query {
+      box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset;
+  }
+  .tt-hint {
+      color: #999999;
+  }
+  .tt-dropdown-menu {
+      background-color: #FFFFFF;
+      border: 1px solid rgba(0, 0, 0, 0.2);
+      border-radius: 8px 8px 8px 8px;
+      box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+      margin-top: 12px;
+      padding: 8px 0;
+      width: 422px;
+  }
+  .tt-suggestion {
+      font-size: 18px;
+      line-height: 24px;
+      padding: 3px 20px;
+  }
+  .tt-suggestion.tt-cursor {
+      background-color: #0097CF;
+      color: #FFFFFF;
+  }
+  .tt-suggestion p {
+      margin: 0;
+  }
+  .tt-dropdown-menu, .gist {
+      text-align: left;
+  }
+  /*
+  html {
+      overflow-y: scroll;
+  }
+  .container {
+      margin: 0 auto;
+      max-width: 750px;
+      text-align: center;
+  }
+
+  html {
+      color: #333333;
+      font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
+      font-size: 18px;
+      line-height: 1.2;
+  }
+  .title, .example-name {
+      font-family: Prociono;
+  }
+  p {
+      margin: 0 0 10px;
+  }
+  .title {
+      font-size: 64px;
+      margin: 20px 0 0;
+  }
+  .example {
+      padding: 30px 0;
+  }
+  .example-name {
+      font-size: 32px;
+      margin: 20px 0;
+  }
+  .demo {
+      margin: 50px 0;
+      position: relative;
+  }
+  */
+  /*
+  .gist {
+      font-size: 14px;
+  }
+  .example-twitter-oss .tt-suggestion {
+      padding: 8px 20px;
+  }
+  .example-twitter-oss .tt-suggestion + .tt-suggestion {
+      border-top: 1px solid #CCCCCC;
+  }
+  .example-twitter-oss .repo-language {
+      float: right;
+      font-style: italic;
+  }
+  .example-twitter-oss .repo-name {
+      font-weight: bold;
+  }
+  .example-twitter-oss .repo-description {
+      font-size: 14px;
+  }
+  .example-sports .league-name {
+      border-bottom: 1px solid #CCCCCC;
+      margin: 0 20px 5px;
+      padding: 3px 0;
+  }
+  .example-arabic .tt-dropdown-menu {
+      text-align: right;
+  }
+  */
diff --git a/utils/test/vnfcatalogue/cronjobs/README.md b/utils/test/vnfcatalogue/cronjobs/README.md
new file mode 100644 (file)
index 0000000..cf27ff8
--- /dev/null
@@ -0,0 +1,24 @@
+# CRONJOB Directory
+
+## Helper to setup cronjob to fill the vnf table
+
+There are two important parameters that need to be set in github.js
+before running cronjob.
+
+
+```
+   access_token : generate an access token from github account for accessing
+   the github apis. This is necessary as the non access token limit tends to 
+   be 50 api calls per hour.
+   delta : the threshold between the last update of the row of the vnf table
+   and current time. It is measured in seconds.
+```
+
+Enter the details namely username and password in the **database.js**.
+Then setup the cronjob by putting the following line in the crontab
+
+In the crontab
+
+```bash
+    node github
+```
diff --git a/utils/test/vnfcatalogue/cronjobs/database.js b/utils/test/vnfcatalogue/cronjobs/database.js
new file mode 100644 (file)
index 0000000..a1d926e
--- /dev/null
@@ -0,0 +1,14 @@
+var mysql = require('mysql');
+
+var pool = mysql.createPool({
+  host: 'localhost',
+  user: 'myuser',
+  password: 'mypassword',
+  database: 'vnf_catalogue',
+  connectionLimit: 50,
+  supportBigNumbers: true,
+  multipleStatements: true,
+  dateStrings: 'date'
+});
+
+exports.pool = pool;
diff --git a/utils/test/vnfcatalogue/cronjobs/github.js b/utils/test/vnfcatalogue/cronjobs/github.js
new file mode 100644 (file)
index 0000000..05cc6c1
--- /dev/null
@@ -0,0 +1,129 @@
+// Important Add your access token here default rate of github is limited to 60 API calls per hour
+var access_token = '*';
+// Important set the delta threshold for repo details updation. For instance if the threshold is
+// set to 1 day(60 * 60 * 24), the cronjob will only update the row if the difference between current
+// time and last_updated time stamp of a repo is greater than one day
+var delta = 60 * 60 * 24;
+
+
+var github = require('octonode');
+db_pool = require('./database').pool;
+async = require('async');
+
+var current_time = Math.floor(new Date().getTime() / 1000);//toISOString().slice(0, 19).replace('T', ' ');
+console.log(current_time);
+
+var get_val_from_header = function(header_link) {
+    // small hack by parsing the header and setting per_page = 1, hence no pagination fetch required
+    result_intermediate = header_link.split(';');
+    result_intermediate = result_intermediate[result_intermediate.length - 2];
+    var reg = /&page=([0-9].*)>/g;
+    var match = reg.exec(result_intermediate);
+    return parseInt(match[1]);
+}
+
+var get_stargazers = function(result, ghrepo, primary_callback, cb) {
+    ghrepo.stargazers({per_page: 1}, function(err, data, headers) {
+    //console.log(JSON.stringify(data));
+        try {
+            result['no_of_stars'] = get_val_from_header(headers['link']);
+            cb(null, result, ghrepo, primary_callback);
+        } catch(err) {
+            result['no_of_stars'] = null;
+            cb(null, result, ghrepo, primary_callback);
+        }
+    });
+}
+
+var get_branches = function(result, ghrepo, primary_callback, cb) {
+    ghrepo.branches({per_page: 1}, function(err, data, headers) {
+        try {
+            result['versions'] = get_val_from_header(headers['link']);
+            cb(null, result, ghrepo, primary_callback);
+        } catch(err) {
+            result['versions'] = null;
+            cb(null, result, ghrepo, primary_callback);
+        }
+    });
+}
+
+var get_contributors = function(result, ghrepo, primary_callback, cb) {
+    ghrepo.contributors({per_page: 1}, function(err, data, headers) {
+        try {
+            result['no_of_developers'] = get_val_from_header(headers['link']);
+            cb(null, result, primary_callback);
+        } catch(err) {
+            result['no_of_developers'] = null;
+            cb(null, result, primary_callback);
+
+        }
+    });
+}
+
+var get_lines_of_code = function(result, cb) {
+    //    #TODO
+}
+
+var secondary_callback = function (err, result, primary_callback) {
+    console.log(result);
+    if((result['last_updated'] == null) || (current_time - result['last_updated'] > delta)) {
+        db_pool.getConnection(function(err, connection) {
+            //Use the connection
+            var last_updated = current_time;
+            var no_of_stars = result['no_of_stars'];
+            var versions = result['versions'];
+            var no_of_developers = result['no_of_developers'];
+            sql_query = 'update vnf set last_updated = FROM_UNIXTIME(' + last_updated;
+            sql_query += '), no_of_stars =  ' + no_of_stars + ', versions = ' + versions;
+            sql_query += ', no_of_developers = ' + no_of_developers + ' where vnf_id = ';
+            sql_query += result['vnf_id'];
+            console.log(sql_query);
+            connection.query(sql_query, function (error, results, fields) {
+                if (error) throw error;
+                //And done with the connection.
+                primary_callback(null, result['vnf_id'] + ' updated');
+                connection.release();
+                // Handle error after the release.
+                // Don't use the connection here, it has been returned to the pool.
+            });
+        });
+    } else {
+        primary_callback(null, result['vnf_id'] + ' not updated');
+    }
+}
+
+var get_stats = function(vnf_details, callback) {
+    repo = vnf_details['repo_url'];
+    repo = repo.split("/");
+    github_id = repo[repo.length - 2] + '/' + repo[repo.length - 1];
+
+    var async = require('async');
+    var client = github.client(access_token);
+    var ghrepo = client.repo(github_id);
+
+    result = {}
+    result['vnf_id'] = vnf_details['vnf_id'];
+    result['last_updated'] = vnf_details['last_updated'];
+
+    async.waterfall([
+            async.apply(get_stargazers, result, ghrepo, callback),
+            get_branches,
+            get_contributors,
+            //get_lines_of_code,
+        ], secondary_callback);
+}
+
+db_pool.getConnection(function(err, connection) {
+    sql_query = 'select vnf_id, repo_url, UNIX_TIMESTAMP(last_updated) last_updated from vnf';
+    console.log(sql_query);
+    connection.query(sql_query, function (error, results, fields) {
+        if (error) throw error;
+        async.map(results, get_stats, function(error, results) {
+            //console.log(results);
+            console.log(results);
+            process.exit();
+
+        });
+    });
+});
+
diff --git a/utils/test/vnfcatalogue/helpers/README.md b/utils/test/vnfcatalogue/helpers/README.md
new file mode 100644 (file)
index 0000000..6c0ca78
--- /dev/null
@@ -0,0 +1,22 @@
+# Helper Directory
+
+## Helper to migrate database
+
+First make sure nodejs and mysql are installed. Then use
+
+```bash
+npm install bookshelf mysql knex when lodash --save
+```
+
+Create a database named **vnf_catalogue**.
+Enter the mysql credentials in migrate.js.
+
+Then use
+
+```bash
+node migrate
+```
+
+If successful the script will return success message. The current script is
+idempotent is nature, if run twice it will just return error and write nothing.
+
diff --git a/utils/test/vnfcatalogue/helpers/migrate.js b/utils/test/vnfcatalogue/helpers/migrate.js
new file mode 100644 (file)
index 0000000..3f4d892
--- /dev/null
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh(penguinRaider) and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+
+var knex = require('knex')({
+    client: 'mysql',
+    connection: {
+        host     : process.env.DB_HOST,
+        user     : process.env.DB_USER,
+        password : process.env.DB_PASSWORD,
+        database : process.env.DB_DATABASE,
+        charset  : 'utf8'
+    }
+});
+var Schema = require('./schema');
+var sequence = require('when/sequence');
+var _ = require('lodash');
+function createTable(tableName) {
+    return knex.schema.createTable(tableName, function (table) {
+    var column;
+    var columnKeys = _.keys(Schema[tableName]);
+    _.each(columnKeys, function (key) {
+        if (Schema[tableName][key].type === 'text' && Schema[tableName][key].hasOwnProperty('fieldtype')) {
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].fieldtype);
+        }
+        else if (Schema[tableName][key].type === 'enum' && Schema[tableName][key].hasOwnProperty('values') && Schema[tableName][key].nullable === true) {
+        console.log(Schema[tableName][key].values);
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].values).nullable();
+        }
+        else if (Schema[tableName][key].type === 'enum' && Schema[tableName][key].hasOwnProperty('values')) {
+        console.log(Schema[tableName][key].values);
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].values).notNullable();
+        }
+        else if (Schema[tableName][key].type === 'string' && Schema[tableName][key].hasOwnProperty('maxlength')) {
+        column = table[Schema[tableName][key].type](key, Schema[tableName][key].maxlength);
+        }
+        else {
+        column = table[Schema[tableName][key].type](key);
+        }
+        if (Schema[tableName][key].hasOwnProperty('nullable') && Schema[tableName][key].nullable === true) {
+        column.nullable();
+        }
+        else {
+        column.notNullable();
+        }
+        if (Schema[tableName][key].hasOwnProperty('primary') && Schema[tableName][key].primary === true) {
+        column.primary();
+        }
+        if (Schema[tableName][key].hasOwnProperty('unique') && Schema[tableName][key].unique) {
+        column.unique();
+        }
+        if (Schema[tableName][key].hasOwnProperty('unsigned') && Schema[tableName][key].unsigned) {
+        column.unsigned();
+        }
+        if (Schema[tableName][key].hasOwnProperty('references')) {
+        column.references(Schema[tableName][key].references);
+        }
+        if (Schema[tableName][key].hasOwnProperty('defaultTo')) {
+        column.defaultTo(Schema[tableName][key].defaultTo);
+        }
+    });
+    });
+}
+function createTables () {
+    var tables = [];
+    var tableNames = _.keys(Schema);
+    tables = _.map(tableNames, function (tableName) {
+    return function () {
+        return createTable(tableName);
+    };
+    });
+    return sequence(tables);
+}
+createTables()
+.then(function() {
+    console.log('Tables created!!');
+    process.exit(0);
+})
+.catch(function (error) {
+    throw error;
+});
diff --git a/utils/test/vnfcatalogue/helpers/schema.js b/utils/test/vnfcatalogue/helpers/schema.js
new file mode 100644 (file)
index 0000000..4a7559a
--- /dev/null
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Kumar Rishabh(penguinRaider) and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *******************************************************************************/
+var Schema = {
+    photo: {
+        photo_id: {type: 'increments', nullable: false, primary: true},
+        photo_url: {type: 'string', maxlength: 254, nullable: false}
+    },
+    user: {
+        user_id: {type: 'increments', nullable: false, primary: true},
+        user_name: {type: 'string', maxlength: 254, nullable: false},
+        password: {type: 'string', maxlength: 150, nullable: false},
+        email_id: {type: 'string', maxlength: 254, nullable: false, unique: true, validations: {isEmail: true}},
+        photo_id: {type: 'integer', nullable: true, unsigned: true, references: 'photo.photo_id'},
+        company: {type: 'string', maxlength: 254, nullable: false},
+        introduction: {type: 'string', maxlength: 510, nullable: false},
+        last_login: {type: 'dateTime', nullable: true},
+        created_at: {type: 'dateTime', nullable: false},
+    },
+    vnf: {
+        vnf_id: {type: 'increments', nullable: false, primary: true},
+        vnf_name: {type: 'string', maxlength: 254, nullable: false},
+        repo_url: {type: 'string', maxlength: 254, nullable: false},
+        photo_id: {type: 'integer', nullable: true, unsigned: true, references: 'photo.photo_id'},
+        submitter_id: {type: 'integer', nullable: false, unsigned: true, references: 'user.user_id'},
+        lines_of_code: {type: 'integer', nullable: true, unsigned: true},
+        versions: {type: 'integer', nullable: true, unsigned: true},
+        no_of_developers: {type: 'integer', nullable: true, unsigned: true},
+        no_of_stars: {type: 'integer', nullable: true, unsigned: true},
+        license: {type: 'enum', nullable: false, values: ['MIT', 'GPL', 'GPL_V2', 'BSD', 'APACHE']},
+        opnfv_indicator: {type: 'enum', nullable: false, values: ['gold', 'silver', 'platinum']},
+        complexity: {type: 'enum', nullable: true, values: ['low', 'medium', 'high']},
+        activity: {type: 'enum', nullable: true, values: ['low', 'medium', 'high']},
+        last_updated: {type: 'dateTime', nullable: true},
+    },
+    tag: {
+        tag_id: {type: 'increments', nullable: false, primary: true},
+        tag_name: {type: 'string', maxlength: 150, nullable: false}
+    },
+    vnf_tags: {
+        vnf_tag_id: {type: 'increments', nullable: false, primary: true},
+        tag_id: {type: 'integer', nullable: false, unsigned: true, references: 'tag.tag_id'},
+        vnf_id: {type: 'integer', nullable: false, unsigned: true, references: 'vnf.vnf_id'},
+    },
+    vnf_contributors: {
+        vnf_contributors_id: {type: 'increments', nullable: false, primary: true},
+        user_id: {type: 'integer', nullable: false, unsigned: true, references: 'user.user_id'},
+        vnf_id: {type: 'integer', nullable: false, unsigned: true, references: 'vnf.vnf_id'},
+        created_at: {type: 'dateTime', nullable: false},
+    }
+};
+module.exports = Schema;