[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Environment (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/custom.md",
    "content": "---\nname: Custom issue template\nabout: Describe this issue template's purpose here.\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "**Is this a BUG REPORT or FEATURE REQUEST?**:\n\n> Uncomment only one, leave it on its own line:\n>\n> /kind bug\n> /kind feature\n\n\n**What happened**:\n\n**What you expected to happen**:\n\n**How to reproduce it (as minimally and precisely as possible)**:\n\n\n**Anything else we need to know?**:\n\n**Environment**:\n- golang version:\n- Kubernetes version (use `kubectl version`):\n- Cloud provider or hardware configuration:\n- OS (e.g. from /etc/os-release):\n- Kernel (e.g. `uname -a`):\n- Install tools:\n- Others:\n\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--  Thanks for sending a pull request!  Here are some tips for you:\n1. If this is your first time, read our contributor guidelines https://github.com/vmware/purser/blob/master/CONTRIBUTING.md \n-->\n\n**What this PR does / why we need it**:\n\n**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:\nFixes #\n\n**Special notes for your reviewer**:\n\n**Release note**:\n<!--  Write your release note:\n1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string \"action required\".\n2. If no release note is required, just write \"NONE\".\n-->\n```release-note\n\n```\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.vscode\n.go/\nbin/\nvendor/\n*.log\n\n# compiled output\n/dist\n/tmp\n/out-tsc\ntmp/\n\n# dependencies\nnode_modules/"
  },
  {
    "path": ".make/Makefile.deploy.controller",
    "content": "IMAGE := $(DOCKER_REPO)/purser\nOUTPUT_DIR := tmp\nDOCKER_OUT := $(OUTPUT_DIR)/docker\n\n.PHONY: build\nbuild: $(DOCKER_OUT)/bin/$(ARCH)/$(BIN)\n\n$(DOCKER_OUT)/bin/$(ARCH)/$(BIN): build-dirs\n\t@echo \"building: $@\"\n\t@docker run                                                            \\\n\t    -ti                                                                \\\n\t    -u $$(id -u):$$(id -g)                                             \\\n\t    -v $$(pwd)/$(DOCKER_OUT)/.go:/go:$(DOCKER_MOUNT_MODE)                            \\\n        -v $$(pwd)/$(BUILD):/go/src/$(PRO)/$(BUILD):$(DOCKER_MOUNT_MODE)   \\\n\t    -v $$(pwd)/$(CMD):/go/src/$(PRO)/$(CMD):$(DOCKER_MOUNT_MODE)                     \\\n\t    -v $$(pwd)/$(PKG):/go/src/$(PRO)/$(PKG):$(DOCKER_MOUNT_MODE)                     \\\n\t    -v $$(pwd)/$(DEP):/go/src/$(PRO)/$(DEP):$(DOCKER_MOUNT_MODE)                     \\\n\t    -v $$(pwd)/$(DOCKER_OUT)/bin/$(ARCH):/go/bin:$(DOCKER_MOUNT_MODE)                \\\n\t    -v $$(pwd)/$(DOCKER_OUT)/bin/$(ARCH):/go/bin/linux_$(ARCH):$(DOCKER_MOUNT_MODE)  \\\n\t    -v $$(pwd)/$(DOCKER_OUT)/.go/std/$(ARCH):/usr/local/go/pkg/linux_$(ARCH)_static:$(DOCKER_MOUNT_MODE)  \\\n\t    -w /go/src                                                 \\\n\t    $(BUILD_IMAGE)                                                     \\\n\t    /bin/sh -c \"                                                       \\\n\t        ARCH=$(ARCH)                                                   \\\n\t        VERSION=$(VERSION)                                             \\\n\t        PKG=$(PKG)                                                     \\\n\t        ./$(PRO)/$(BUILD)/build.sh                                               \\\n\t    \"\n\nDOTFILE_IMAGE = $(subst :,_,$(subst /,_,$(IMAGE))-$(VERSION))\n\n.PHONY: container\ncontainer: $(DOCKER_OUT)/.container-$(DOTFILE_IMAGE) container-name\n$(DOCKER_OUT)/.container-$(DOTFILE_IMAGE): $(DOCKER_OUT)/bin/$(ARCH)/$(BIN) Dockerfile.in\n\t@sed \\\n\t    -e 's|ARG_DOCK|$(DOCKER_OUT)|g' \\\n\t    -e 's|ARG_BIN|$(BIN)|g' \\\n\t    -e 's|ARG_ARCH|$(ARCH)|g' \\\n\t    -e 's|ARG_FROM|$(BASEIMAGE)|g' \\\n\t    Dockerfile.in > $(DOCKER_OUT)/.dockerfile-$(ARCH)\n\t@docker build -t $(IMAGE):$(VERSION) -f $(DOCKER_OUT)/.dockerfile-$(ARCH) .\n\t@docker images -q $(IMAGE):$(VERSION) > $@\n\n.PHONY: container-name\ncontainer-name:\n\t@echo \"container: $(IMAGE):$(VERSION)\"\n\n.PHONY: push\npush: $(DOCKER_OUT)/.push-$(DOTFILE_IMAGE) push-name\n$(DOCKER_OUT)/.push-$(DOTFILE_IMAGE): $(DOCKER_OUT)/.container-$(DOTFILE_IMAGE)\nifeq ($(findstring gcr.io,$(DOCKER_REPO)),gcr.io)\n\t@gcloud docker -- push $(IMAGE):$(VERSION)\nelse\n\t@docker push $(IMAGE):$(VERSION)\nendif\n\t@docker images -q $(IMAGE):$(VERSION) > $@\n\n.PHONY: push-name\npush-name:\n\t@echo \"pushed: $(IMAGE):$(VERSION)\"\n\n.PHONY: build-dirs\nbuild-dirs:\n\t@mkdir -p $(DOCKER_OUT)\n\t@mkdir -p $(DOCKER_OUT)/bin/$(ARCH)\n\t@mkdir -p $(DOCKER_OUT)/.go/src/$(PKG) $(DOCKER_OUT)/.go/pkg $(DOCKER_OUT)/.go/bin $(DOCKER_OUT)/.go/std/$(ARCH)\n\n.PHONY: clean\nclean: container-clean bin-clean\n\n.PHONY: container-clean\ncontainer-clean:\n\trm -rf $(DOCKER_OUT)/.container-* $(DOCKER_OUT)/.dockerfile-* $(DOCKER_OUT)/.push-*\n\n.PHONY: bin-clean\nbin-clean:\n\trm -rf $(DOCKER_OUT)/\n\n"
  },
  {
    "path": ".make/Makefile.deploy.purser",
    "content": "DEPLOY_DOCKERFILE?=ui/Dockerfile.deploy.purser\n\nCLUSTER_DIR?=${PWD}/cluster\n\nCOMMIT:=$(shell git rev-parse --short HEAD)\nTIMESTAMP:=$(shell date +%s)\nTAG?=$(COMMIT)-$(TIMESTAMP)\n\n.PHONY: deploy-purser\ndeploy-purser: kubectl-deploy-purser-db kubectl-deploy-purser-ui\n\n.PHONY: kubectl-deploy-purser-ui\nkubectl-deploy-purser-ui: \n\t@echo \"Deploys purser-ui service\"\n\t@kubectl create -f $(CLUSTER_DIR)/purser-ui.yaml \n\n.PHONY: deploy-purser-ui\ndeploy-purser-ui: build-purser-ui-image push-purser-ui-image\n\n.PHONY: build-purser-ui-image\nbuild-purser-ui-image:\n\t@docker build --build-arg BINARY=purser-ui -t $(REGISTRY)/$(DOCKER_REPO)/purser-ui -f $(DEPLOY_DOCKERFILE) .\n\t@docker tag $(REGISTRY)/$(DOCKER_REPO)/purser-ui $(REGISTRY)/$(DOCKER_REPO)/purser-ui:$(TAG)\n\n.PHONY: push-purser-ui-image\npush-purser-ui-image: build-purser-ui-image\n\t@docker push $(REGISTRY)/$(DOCKER_REPO)/purser-ui\n\n.PHONY: clean-purser-ui-image\nclean-purser-ui-image:\n\t@docker rmi -f $(REGISTRY)/$(DOCKER_REPO)/purser-ui\n\n.PHONY: kubectl-deploy-purser-db\nkubectl-deploy-purser-db: \n\t@echo \"Deploys purser purser-db service\"\n\t@kubectl create -f $(CLUSTER_DIR)/purser-db.yaml\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "services:\n  - docker\n\nlanguage: go\n\nos:\n  - linux\n\ngo:\n  - \"1.10\"\n\nscript:\n  - make tools\n  - make deps\n  - make install\n  - make travis-build\n  - make check\n\nnotifications:\n  email: false\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "Contributor Code of Conduct\n======================\n\nAs contributors and maintainers of this project, we pledge to respect\neveryone who contributes by posting issues, updating documentation,\nsubmitting pull requests, providing feedback in comments, and any other\nactivities.\n\nCommunication through any project channels (GitHub, mailing lists,\nTwitter, and so on) must be constructive and never resort to personal\nattacks, trolling, public or private harassment, insults, or other\nunprofessional conduct.\n\nWe promise to extend courtesy and respect to everyone involved in\nthis project, regardless of gender, gender identity, sexual\norientation, disability, age, race, ethnicity, religious beliefs,\nor level of experience. We expect anyone contributing to this project\nto do the same.\n\nIf any member of the community violates this code of conduct, the\nmaintainers of this project may take action, including removing issues,\ncomments, and PRs or blocking accounts, as deemed appropriate.\n\nIf you are subjected to or witness unacceptable behavior, or have any\nother concerns, please communicate with us.\n\nIf you have suggestions to improve the code of conduct, please submit\nan issue or PR.\n\n\n**Attribution**\n\nThis Code of Conduct is adapted from the VMware Clarity project, available at this page: https://github.com/vmware/clarity/blob/master/CODE_OF_CONDUCT.md\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Purser\n\nWelcome! We gladly accept contributions from the community. If you wish\nto contribute code and you have not signed our contributor license\nagreement (CLA), our bot will update the issue when you open a pull\nrequest. For any questions about the CLA process, please refer to our\n[FAQ](https://cla.vmware.com/faq).\n\n## Logging Bugs\n\nAnyone can log a bug using the GitHub 'New Issue' button.  Please use\na short title and give as much information as you can about what the\nproblem is, relevant software versions, and how to reproduce it.  If you\nknow the fix or a workaround include that too.\n\n## Install dependencies\n- Install [git](https://git-scm.com/downloads)\n- Install [Go](https://golang.org/dl/) version at least 1.7\n- Set GOPATH environment variable. [https://github.com/golang/go/wiki/SettingGOPATH](https://github.com/golang/go/wiki/SettingGOPATH)\n- Add GOPATH/bin in system PATH variable\n\n## Code Contribution Flow\n\nWe use GitHub pull requests to incorporate code changes from external\ncontributors.  Typical contribution flow steps are:\n\n- Fork the Purser repo into a new repo on GitHub\n- Clone the forked repo locally and set the original Purser repo as the upstream repo\n- Make changes in a topic branch and commit\n- Fetch changes from upstream and resolve any merge conflicts so that your topic branch is up-to-date\n- Push all commits to the topic branch in your forked repo\n- Submit a pull request to merge topic branch commits to upstream master\n\nIf this process sounds unfamiliar have a look at the\nexcellent [overview of collaboration via pull requests on\nGitHub](https://help.github.com/categories/collaborating-with-issues-and-pull-requests) for more information. \n\n## Coding Style\n\nOur standard for Golang contributions is to match the format of the [standard\nGo package library](https://golang.org/pkg).  \n\n- Run `go fmt` on all code.\n- All public interfaces, functions, and structs must have complete, grammatically correct Godoc comments that explain their purpose and proper usage.\n- Use self-explanatory names for all variables, functions, and interfaces.\n- Add comments for non-obvious features of internal implementations but otherwise let the code explain itself.\n- Include unit tests for new features and update tests for old ones.\n\nGo is pretty readable so if you follow these rules most functions\nwill not need additional comments.\n\n### Commit Message Format\n\nWe follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/).\n\nBe sure to include any related GitHub\nissue references in the commit message.  See [GFM\nsyntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown)\nfor referencing issues.\n\n### Sign the Contributor License Agreement (CLA)\n\nVMware Apache-licensed projects require all contributors to sign a CLA. \nVisit https://cla.vmware.com and follow steps presented there. \n\n### Fork the Repo\n\nNavigate to the [Purser repo on\nGitHub](https://github.com/vmware/purser) and use the 'Fork' button to\ncreate a forked repository under your GitHub account.  This gives you a copy \nof the repo for pull requests back to purser in https://github.com/your-github-id/purser\n\n### Clone and Set Upstream Remote\n\nMake a local clone of the forked repo and add the base purser\nrepo as the upstream remote repository.\n\n``` shell\n# (go to directory $GOPATH/src/github.com/vmware)\ncd $GOPATH/src/github.com/vmware\n# (clone the forked repository)\ngit clone https://github.com/<your-github-id>/purser.git\n# (go to purser directory)\ncd $GOPATH/src/github.com/vmware/purser\n# (add upstream repository as the original purser repo)\ngit remote add upstream https://github.com/vmware/purser.git\n```\n\nThe last git command prepares your clone to pull changes from the\nupstream repo and push them into the fork, which enables you to keep\nthe fork up to date. More on that shortly.\n\n### Download dependencies\n\nRun the following commands to download dependencies.\n\n``` shell\nmake tools\nmake deps\nmake install\n```\n\n### Make Changes and Commit\n\nStart a new topic branch(say branch-name: `foo-api-fix-22`) from the current HEAD position on master and\ncommit your feature changes into that branch.\n\n``` shell\ngit checkout -b foo-api-fix-22 master\n# (Make feature changes)\n```\n\nEnsure that you run the following commands to ensure new dependencies are recorded and to fix formatting of the code.\n\n``` shell\nmake update\nmake format\nmake check\n```\n\nIf there is an error while running `make check`, fix them and re run the above commands.\n\n``` shell\ngit commit -a --signoff\ngit push origin foo-api-fix-22\n```\n\nThe --signoff puts your signature in the commit.  It's required by our CLA\nbot. \n\nIt is a git best practice to put work for each new feature in a separate\ntopic branch and use git checkout commands to jump between them.  This\nmakes it possible to have multiple active pull requests.  We can accept\npull requests from any branch, so it's up to you how to manage them.\n\n### Stay in Sync with Upstream\n\nFrom time to time you'll need to merge changes from the upstream\nrepo so your topic branch stays in sync with other checkins.  To\ndo so switch to your topic branch, pull from the upstream repo, and\npush into the fork.  If there are conflicts you'll need to [merge\nthem now](https://stackoverflow.com/questions/161813/how-to-resolve-merge-conflicts-in-git).\n\n``` shell\ngit checkout foo-api-fix-22\ngit fetch -a\ngit pull --rebase upstream master --tags\ngit push --force-with-lease origin foo-api-fix-22\n```\n\nThe git pull and push options are important.  Here are some details if you \nneed deeper understanding. \n\n- 'pull --rebase' eliminates unnecessary merges\nby replaying your commit(s) into the log as if they happened\nafter the upstream changes.  Check out [What is a \"merge\nbubble\"?](https://stackoverflow.com/questions/26239379/what-is-a-merge-bubble)\nfor why this is important.  \n- --tags ensures that object tags are also pulled\n- Depending on your git configuration push --force-with-lease is required to make git update your fork with commits from the upstream repo.\n\n### Create a Pull Request\nGithub docs on creating a pull request from a fork: [Pull request from a fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)\nTo contribute your feature, create a pull request by going to the [purser upstream repo on GitHub](https://github.com/vmware/purser) and pressing the 'New pull request' button. \n\nSelect 'compare across forks' and select your-github-id/purser as 'head fork'\nand foo-api-fix-22 as the 'compare' branch.  Leave the base fork as \nvmware/purser and master. \n\n### Wait...\n\nA committer will look the request over and do one of three things: \n\n- accept it\n- send back comments about things you need to fix\n- or close the request without merging if we don't think it's a good addition.\n\n### Updating Pull Requests with New Changes\n\nIf your pull request needs changes based on code review, \nyou'll most likely want to squash the fixes into existing commits.\n\nIf your pull request contains a single commit or your changes are related\nto the most recent commit, you can simply amend the commit.\n\n``` shell\ngit add .\ngit commit --amend\ngit push --force-with-lease origin foo-api-fix-22\n```\n\nIf you need to squash changes into an earlier commit, you can use:\n\n``` shell\ngit add .\ngit commit --fixup <commit>\ngit rebase -i --autosquash master\ngit push --force-with-lease origin foo-api-fix-22\n```\n\nBe sure to add a comment to the pull request indicating your new changes\nare ready to review, as GitHub does not generate a notification when\nyou git push.\n\n## Final Words\n\nThanks for helping us make the project better!\n"
  },
  {
    "path": "Dockerfile.in",
    "content": "FROM ARG_FROM\n\nLABEL maintainer = \"VMware <kreddyj@vmware.com>\"\nLABEL author = \"Krishna Karthik <kreddyj@vmware.com>\"\n\nADD ARG_DOCK/bin/ARG_ARCH/ARG_BIN /ARG_BIN"
  },
  {
    "path": "Gopkg.toml",
    "content": "# Gopkg.toml example\n#\n# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html\n# for detailed Gopkg.toml documentation.\n#\n# required = [\"github.com/user/thing/cmd/thing\"]\n# ignored = [\"github.com/user/project/pkgX\", \"bitbucket.org/user/project/pkgA/pkgY\"]\n#\n# [[constraint]]\n#   name = \"github.com/user/project\"\n#   version = \"1.0.0\"\n#\n# [[constraint]]\n#   name = \"github.com/user/project2\"\n#   branch = \"dev\"\n#   source = \"github.com/myfork/project2\"\n#\n# [[override]]\n#   name = \"github.com/x/y\"\n#   version = \"2.4.0\"\n#\n# [prune]\n#   non-go = false\n#   go-tests = true\n#   unused-packages = true\n\n\n[[constraint]]\n  name = \"k8s.io/api\"\n  version = \"kubernetes-1.9.0\"\n\n[[constraint]]\n  name = \"k8s.io/apiextensions-apiserver\"\n  version = \"kubernetes-1.9.0\"\n\n[[constraint]]\n  name = \"k8s.io/apimachinery\"\n  version = \"kubernetes-1.9.0\"\n\n[[constraint]]\n  name = \"k8s.io/client-go\"\n  version = \"6.0.0\"\n\n[[constraint]]\n  name = \"google.golang.org/grpc\"\n  version = \"1.15.0\"\n\n[[constraint]]\n  name = \"github.com/dgraph-io/dgo\"\n  branch = \"master\"\n\n[[override]]\n  name = \"github.com/tidwall/gjson\"\n  version = \"1.1.2\"\n\n[prune]\n  go-tests = true\n  unused-packages = true\n\n"
  },
  {
    "path": "LICENSE",
    "content": "Purser\r\n\r\nCopyright (c) 2018 VMware, Inc.  All rights reserved.\t\t\t\t\r\n\r\nThe Apache 2.0 license (the License) set forth below applies to all parts of the Purser\r\nproject.  You may not use this file except in compliance with the License.\r\n\r\nApache License \r\n\r\nVersion 2.0, January 2004 \r\nhttp://www.apache.org/licenses/ \r\n\r\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION \r\n\r\n1. Definitions.\r\n\r\n\"License\" shall mean the terms and conditions for use, reproduction,\r\nand distribution as defined by Sections 1 through 9 of this document.\r\n\r\n\"Licensor\" shall mean the copyright owner or entity authorized by the\r\ncopyright owner that is granting the License.  \r\n\r\n\"Legal Entity\" shall mean the union of the acting entity and all other\r\nentities that control, are controlled by, or are under common control\r\nwith that entity. For the purposes of this definition, \"control\" means\r\n(i) the power, direct or indirect, to cause the direction or management\r\nof such entity, whether by contract or otherwise, or (ii) ownership\r\nof fifty percent (50%) or more of the outstanding shares, or (iii)\r\nbeneficial ownership of such entity.\r\n\r\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\r\npermissions granted by this License.  \r\n\r\n\"Source\" form shall mean the preferred form for making modifications,\r\nincluding but not limited to software source code, documentation source,\r\nand configuration files.\r\n\r\n\"Object\" form shall mean any form resulting from mechanical transformation\r\nor translation of a Source form, including but not limited to compiled\r\nobject code, generated documentation, and conversions to other media\r\ntypes.  \r\n\r\n\"Work\" shall mean the work of authorship, whether in Source or\r\nObject form, made available under the License, as indicated by a copyright\r\nnotice that is included in or attached to the work (an example is provided\r\nin the Appendix below).  \r\n\r\n\"Derivative Works\" shall mean any work, whether in Source or Object form,\r\nthat is based on (or derived from) the Work and for which the editorial\r\nrevisions, annotations, elaborations, or other modifications represent,\r\nas a whole, an original work of authorship. For the purposes of this\r\nLicense, Derivative Works shall not include works that remain separable\r\nfrom, or merely link (or bind by name) to the interfaces of, the Work\r\nand Derivative Works thereof.\r\n\r\n\"Contribution\" shall mean any work of authorship, including the\r\noriginal version of the Work and any modifications or additions to\r\nthat Work or Derivative Works thereof, that is intentionally submitted\r\nto Licensor for inclusion in the Work by the copyright owner or by an\r\nindividual or Legal Entity authorized to submit on behalf of the copyright\r\nowner. For the purposes of this definition, \"submitted\" means any form of\r\nelectronic, verbal, or written communication sent to the Licensor or its\r\nrepresentatives, including but not limited to communication on electronic\r\nmailing lists, source code control systems, and issue tracking systems\r\nthat are managed by, or on behalf of, the Licensor for the purpose of\r\ndiscussing and improving the Work, but excluding communication that is\r\nconspicuously marked or otherwise designated in writing by the copyright\r\nowner as \"Not a Contribution.\"\r\n\r\n\"Contributor\" shall mean Licensor and any individual or Legal Entity\r\non behalf of whom a Contribution has been received by Licensor and\r\nsubsequently incorporated within the Work.\r\n\r\n2. Grant of Copyright License.\r\nSubject to the terms and conditions of this License, each Contributor\r\nhereby grants to You a perpetual, worldwide, non-exclusive, no-charge,\r\nroyalty-free, irrevocable copyright license to reproduce, prepare\r\nDerivative Works of, publicly display, publicly perform, sublicense, and\r\ndistribute the Work and such Derivative Works in Source or Object form.\r\n\r\n3. Grant of Patent License.\r\nSubject to the terms and conditions of this License, each Contributor\r\nhereby grants to You a perpetual, worldwide, non-exclusive, no-charge,\r\nroyalty- free, irrevocable (except as stated in this section) patent\r\nlicense to make, have made, use, offer to sell, sell, import, and\r\notherwise transfer the Work, where such license applies only to those\r\npatent claims licensable by such Contributor that are necessarily\r\ninfringed by their Contribution(s) alone or by combination of\r\ntheir Contribution(s) with the Work to which such Contribution(s)\r\nwas submitted. If You institute patent litigation against any entity\r\n(including a cross-claim or counterclaim in a lawsuit) alleging that the\r\nWork or a Contribution incorporated within the Work constitutes direct\r\nor contributory patent infringement, then any patent licenses granted\r\nto You under this License for that Work shall terminate as of the date\r\nsuch litigation is filed.\r\n\r\n4. Redistribution.\r\nYou may reproduce and distribute copies of the Work or Derivative Works\r\nthereof in any medium, with or without modifications, and in Source or\r\nObject form, provided that You meet the following conditions:\r\n\r\n  a. You must give any other recipients of the Work or Derivative Works\r\n     a copy of this License; and\r\n\r\n  b. You must cause any modified files to carry prominent notices stating\r\n     that You changed the files; and\r\n\r\n  c. You must retain, in the Source form of any Derivative Works that\r\n     You distribute, all copyright, patent, trademark, and attribution\r\n     notices from the Source form of the Work, excluding those notices\r\n     that do not pertain to any part of the Derivative Works; and\r\n\r\n  d. If the Work includes a \"NOTICE\" text file as part of its\r\n     distribution, then any Derivative Works that You distribute must\r\n     include a readable copy of the attribution notices contained\r\n     within such NOTICE file, excluding those notices that do not\r\n     pertain to any part of the Derivative Works, in at least one of\r\n     the following places: within a NOTICE text file distributed as part\r\n     of the Derivative Works; within the Source form or documentation,\r\n     if provided along with the Derivative Works; or, within a display\r\n     generated by the Derivative Works, if and wherever such third-party\r\n     notices normally appear. The contents of the NOTICE file are for\r\n     informational purposes only and do not modify the License. You\r\n     may add Your own attribution notices within Derivative Works that\r\n     You distribute, alongside or as an addendum to the NOTICE text\r\n     from the Work, provided that such additional attribution notices\r\n     cannot be construed as modifying the License.  You may add Your own\r\n     copyright statement to Your modifications and may provide additional\r\n     or different license terms and conditions for use, reproduction, or\r\n     distribution of Your modifications, or for any such Derivative Works\r\n     as a whole, provided Your use, reproduction, and distribution of the\r\n     Work otherwise complies with the conditions stated in this License.\r\n\r\n5. Submission of Contributions.\r\nUnless You explicitly state otherwise, any Contribution intentionally\r\nsubmitted for inclusion in the Work by You to the Licensor shall be\r\nunder the terms and conditions of this License, without any additional\r\nterms or conditions.  Notwithstanding the above, nothing herein shall\r\nsupersede or modify the terms of any separate license agreement you may\r\nhave executed with Licensor regarding such Contributions.\r\n\r\n6. Trademarks.\r\nThis License does not grant permission to use the trade names, trademarks,\r\nservice marks, or product names of the Licensor, except as required for\r\nreasonable and customary use in describing the origin of the Work and\r\nreproducing the content of the NOTICE file.\r\n\r\n7. Disclaimer of Warranty.\r\nUnless required by applicable law or agreed to in writing, Licensor\r\nprovides the Work (and each Contributor provides its Contributions) on\r\nan \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\r\nexpress or implied, including, without limitation, any warranties or\r\nconditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR\r\nA PARTICULAR PURPOSE. You are solely responsible for determining the\r\nappropriateness of using or redistributing the Work and assume any risks\r\nassociated with Your exercise of permissions under this License.\r\n\r\n8. Limitation of Liability.\r\nIn no event and under no legal theory, whether in tort (including\r\nnegligence), contract, or otherwise, unless required by applicable law\r\n(such as deliberate and grossly negligent acts) or agreed to in writing,\r\nshall any Contributor be liable to You for damages, including any direct,\r\nindirect, special, incidental, or consequential damages of any character\r\narising as a result of this License or out of the use or inability to\r\nuse the Work (including but not limited to damages for loss of goodwill,\r\nwork stoppage, computer failure or malfunction, or any and all other\r\ncommercial damages or losses), even if such Contributor has been advised\r\nof the possibility of such damages.\r\n\r\n9. Accepting Warranty or Additional Liability.\r\nWhile redistributing the Work or Derivative Works thereof, You may\r\nchoose to offer, and charge a fee for, acceptance of support, warranty,\r\nindemnity, or other liability obligations and/or rights consistent with\r\nthis License. However, in accepting such obligations, You may act only\r\non Your own behalf and on Your sole responsibility, not on behalf of\r\nany other Contributor, and only if You agree to indemnify, defend, and\r\nhold each Contributor harmless for any liability incurred by, or claims\r\nasserted against, such Contributor by reason of your accepting any such\r\nwarranty or additional liability.\r\n\r\nEND OF TERMS AND CONDITIONS \r\n\r\n\r\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#    http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# The binary to build (just the basename).\nBIN := controller\n\n# This repo's root import path (under GOPATH)\nPRO := github.com/vmware/purser\nDEP := vendor\nBUILD := build\nPKG := pkg\nCMD := cmd/controller\n\n# Where to push the docker image.\nREGISTRY?=docker.io\nDOCKER_REPO?=kreddyj\n\n# Which architecture to build - see $(ALL_ARCH) for options.\nARCH?= amd64\n\n# This version-strategy uses a manual value to set the version string\nVERSION := controller-1.0.2\n\n###\n### These variables should not need tweaking.\n###\n\nALL_ARCH := amd64 arm arm64 ppc64le\nBASEIMAGE?=photon\nBUILD_IMAGE?=golang:1.11\nDOCKER_MOUNT_MODE=delegated\n\n# Set dep management tool parameters\nVENDOR_DIR := vendor\nDEP_BIN_NAME := dep\nDEP_BIN_DIR := ./tmp/bin\nDEP_BIN := $(DEP_BIN_DIR)/$(DEP_BIN_NAME)\nDEP_VERSION := v0.5.0\n\n# Define and get the vakue for UNAME_S variable from shell\nUNAME_S := $(shell uname -s)\n\n.PHONY: travis-build\ntravis-build: install-plugin install-controller travis-success\n\n.PHONY: install-plugin\ninstall-plugin:\n\tgo install github.com/vmware/purser/cmd/plugin\n\n.PHONY: install-controller\ninstall-controller: build container\n\n.PHONY: travis-success\ntravis-success:\n\t@echo \"travis build success\"\n\n# If you want to build all binaries, see the 'all-build' rule.\n# If you want to build all containers, see the 'all-container' rule.\n# If you want to build AND push all containers, see the 'all-push' rule.\n.PHONY: all\nall: deps build check\n\nbuild-%:\n\t@$(MAKE) --no-print-directory ARCH=$* build\n\ncontainer-%:\n\t@$(MAKE) --no-print-directory ARCH=$* container\n\npush-%:\n\t@$(MAKE) --no-print-directory ARCH=$* push\n\n.PHONY: all-build\nall-build: $(addprefix build-, $(ALL_ARCH))\n\n.PHONY: all-container\nall-container: $(addprefix container-, $(ALL_ARCH))\n\n.PHONY: all-push\nall-push: $(addprefix push-, $(ALL_ARCH))\n\n.PHONY: deps\n## Download build dependencies.\ndeps: $(DEP_BIN) $(VENDOR_DIR)\n\n# install dep in a the tmp/bin dir of the repo\n$(DEP_BIN):\n\t@echo \"Installing 'dep' $(DEP_VERSION) at '$(DEP_BIN_DIR)'...\"\n\tmkdir -p $(DEP_BIN_DIR)\nifeq ($(UNAME_S),Darwin)\n\t@curl -L -s https://github.com/golang/dep/releases/download/$(DEP_VERSION)/dep-darwin-amd64 -o $(DEP_BIN)\n\t@cd $(DEP_BIN_DIR) && \\\n\techo \"1a7bdb0d6c31ecba8b3fd213a1170adf707657123e89dff234871af9e0498be2  dep\" > dep-darwin-amd64.sha256 && \\\n\tshasum -a 256 --check dep-darwin-amd64.sha256\nelse\n\t@curl -L -s https://github.com/golang/dep/releases/download/$(DEP_VERSION)/dep-linux-amd64 -o $(DEP_BIN)\n\t@cd $(DEP_BIN_DIR) && \\\n\techo \"287b08291e14f1fae8ba44374b26a2b12eb941af3497ed0ca649253e21ba2f83  dep\" > dep-linux-amd64.sha256 && \\\n\tsha256sum -c dep-linux-amd64.sha256\nendif\n\t@chmod +x $(DEP_BIN)\n\n$(VENDOR_DIR): Gopkg.toml Gopkg.lock\n\t@echo \"checking dependencies...\"\n\t@$(DEP_BIN) ensure -v\n\n.PHONY: install\ninstall: ## Fetches all dependencies using dep\n\t@$(DEP_BIN) ensure -v\n\n.PHONY: update\nupdate: ## Updates all dependencies defined for dep\n\t@$(DEP_BIN) ensure -update -v\n\ninclude ./.make/Makefile.deploy.controller\ninclude ./.make/Makefile.deploy.purser\t\n\n.PHONY: version\nversion:\n\t@echo $(VERSION)\n\n.PHONY: clean-vendor ## Removes the ./vendor directory.\nclean-vendor:\n\t-rm -rf $(VENDOR_DIR)\n\nGOFORMAT_FILES := $(shell find  . -name '*.go' | grep -v /vendor/)\n\n.PHONY: format ## Formats any go file that differs from gofmt's style and removes unused imports\nformat: \n\t@gofmt -s -l -w ${GOFORMAT_FILES}\n\t@goimports -l -w ${GOFORMAT_FILES}\n\n.PHONY: tools\ntools: ## Installs required go tools\n\t@go get -u github.com/alecthomas/gometalinter && gometalinter --install\n\t@go get -u golang.org/x/tools/cmd/goimports\n\t\n.PHONY: check\ncheck: ## Concurrently runs a whole bunch of static analysis tools\n\tgometalinter --enable=misspell --enable-gc --vendor --deadline 300s ./...\n"
  },
  {
    "path": "NOTICE",
    "content": "Purser\r\n\r\nCopyright (c) 2018 VMware, Inc. All Rights Reserved. \r\n\r\nThis product is licensed to you under the Apache 2.0 license (the \"License\").\r\nYou may not use this product except in compliance with the Apache 2.0 License.\r\n\r\nThis product may include a number of subcomponents with separate copyright notices and license terms.\r\nYour use of these subcomponents is subject to the terms and conditions of the subcomponent's license,\r\nas noted in the LICENSE file.\r\n\r\n"
  },
  {
    "path": "README.md",
    "content": "![logo](https://user-images.githubusercontent.com/42761785/53145168-2f4e4980-35c5-11e9-867b-8d637671ec23.png)\n# K8s Extension for Application Visibility\n\n[![Build Status](https://travis-ci.org/vmware/purser.svg?branch=master)](https://travis-ci.org/vmware/purser) [![Go Report Card](https://goreportcard.com/badge/github.com/vmware/purser)](https://goreportcard.com/report/github.com/vmware/purser)\n\n- [What is Purser?](#overview)\n- [Features](#features)\n- [Setup and Installation](#setup-and-installation)\n- [Uninstalling](#uninstalling)\n- [API Documentation](#api-documentation)\n- [Additional Documentation](#additional-documentation)\n- [Community, Discussion, Contribution and Support](#community-discussion-contribution-and-support)\n\n## Overview\n\nPurser is an extension to Kubernetes tasked at providing an insight into *cluster topology*, *costing*, *capacity allocations* and *resource interactions* along with the provision of *logical grouping of resources* for Kubernetes based cloud native applications in a cloud neutral manner, with the focus on catering to a multitude of users ranging from Sys Admins, to DevOps to Developers.\n\nIt comprises of three components: a controller, a plugin and a UI dashboard.  \n\nThe controller component deployed inside the cluster watches for K8s native and custom resources associated with the application, thereby, periodically building not just an inventory but also performing discovery by generating and storing the interactions among the resources such as containers, pods and services.\n\nThe plugin component is a CLI tool interfacing with the `kubectl` that helps query costs, savings defined at a level of control of the application level components  rather than at the infrastructure level.\n\nThe UI dashboard is a robust application that renders the Purser UI for providing visual representation to the complete cluster metrics in a single pane of glass. \n\n## Features\n\nPurser with its robust CLI and UI capabilities provides a set of features including, but not limited to the list below.\n \n - Capability to provide visibility into the following aspects of the K8s cluster\n    - workload cost associated with the native/custom resources\n    - savings opportunities associated with storage and compute requirements\n    - single pane view of the complete cluster hierarchy\n    - capacity allocations for CPU, memory, disk space and other resources\n    - interactions among associated resources such as pods and services\n \n - Capability of user defined logical grouping of resources based on `K8s CRD` implementation for enhanced filtering.\n \n - A plugin extension to `kubectl` along with the UI for developer centric usage.\n \n - Capability to subscribe to inventory changes via web-hook implementation. \n\n### UI Demo\n\n ![demo](https://user-images.githubusercontent.com/42461220/54865566-35367680-4d8d-11e9-9e07-e9aa77d7c6ec.gif)\n\n### CLI Demo\n\n ![demo](/docs/img/purser-cli.gif)\n\n## Setup and Installation\n\n### Prerequisites\n- Kubernetes version 1.9 or greater.\n- `kubectl` installed and configured. For details see [here](https://kubernetes.io/docs/tasks/tools/install-kubectl/).\n- `curl` installed. Download it [here](https://curl.haxx.se/download.html)\n\n### Linux/Mac Users:\n```bash\ncurl https://raw.githubusercontent.com/vmware/purser/master/build/purser-setup.sh -O && sh purser-setup.sh\n```\n\n_NOTE: If you want to try out purser on minikube, you can do the following steps instead._\n```bash\ncurl https://raw.githubusercontent.com/vmware/purser/master/build/purser-minimal-setup.sh -O && sh purser-minimal-setup.sh\n\n# Wait for containers to start, around 30s\n# Open Purser in browser\nminikube service purser-ui -n purser\n```\n\n### Windows/Other Users:\n\nFor detailed installation steps follow the instructions in the [manual installation guide](./docs/manual-installation.md).\n\n\n### Purser Plugin Setup (Optional)\n_NOTE: This Plugin installation is optional. This feature is not actively maintained and will be deprecated soon._\n\nIf you want to install and use Purser's command line interface\n- [Plugin installation guide](./docs/plugin-installation.md).\n- [Plugin Usage](./docs/plugin-usage.md).\n\n\n### Other Installation Methods\n\nFor other installation methods such as **manual installation** or **installation from source code** refer guides in [docs](./docs).\n\n### Uninstalling\n\n``` bash\nkubectl delete ns purser\n```\n\n_**NOTE:** Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._\n\n\n## API Documentation\n\nThe project uses Swagger to document API's endpoints. The documentation is available at [Swagger Hub](https://app.swaggerhub.com/apis/hemani19/purser/1.0.0).\n\n## Additional Documentation\n\nAdditional documentation can be found below:\n\n- [Manual Installation Guide](./docs/manual-installation.md)\n- [Source Code Installation Guide](./docs/sourcecode-installation.md)\n- [Purser Architecture and Workflow](./docs/architecture.md)\n- [Purser Plugin Usage](./docs/plugin-usage.md)\n- [Developers Guide](./docs/developers-guide.md)\n- [Purser Deployment Guide](./docs/purser-deployment.md)\n- [Purser UI Development Guide](./ui/README.md)\n\n## Community, Discussion, Contribution and Support\n\n**Issues:** Have an issue with Purser, please [log it](https://github.com/vmware/purser/issues).\n\n**Contributing:** Would you like to contribute to our project, refer [How to contribute](./CONTRIBUTING.md), [Developers Guide](./docs/developers-guide.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) docs.\n"
  },
  {
    "path": "build/build.sh",
    "content": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#    http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nset -o errexit\nset -o nounset\n\nif [ -z \"${PKG}\" ]; then\n    echo \"PKG must be set\"\n    exit 1\nfi\nif [ -z \"${ARCH}\" ]; then\n    echo \"ARCH must be set\"\n    exit 1\nfi\nif [ -z \"${VERSION}\" ]; then\n    echo \"VERSION must be set\"\n    exit 1\nfi\n\nexport CGO_ENABLED=0\nexport GOARCH=\"${ARCH}\"\n\ngo install                                                         \\\n    -installsuffix \"static\"                                        \\\n    -ldflags \"-X ${PKG}/version.VERSION=${VERSION}\"            \\\n    ./...\n"
  },
  {
    "path": "build/purser-binary-install.sh",
    "content": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#    http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# Realease Version\nreleaseVersion=v1.0.0\n\n# === Purser Plugin ===\n\n# Detecting os type\nunameOut=\"$(uname -s)\"\ncase \"${unameOut}\" in\n    Linux*)     machine=Linux;;\n    Darwin*)    machine=Mac;;\n    CYGWIN*)    machine=Cygwin;;\n    MINGW*)     machine=MinGw;;\n    *)          machine=\"UNKNOWN:${unameOut}\"\nesac\necho \"Detecting your Operating System: ${machine}\"\n\necho \"Downloading files for plugin...\"\n# Download purser plugin yaml\npluginYamlUrl=https://github.com/vmware/purser/releases/download/$releaseVersion/plugin.yaml\nwget -q --show-progress -O plugin.yaml $pluginYamlUrl\n\n# Downloading purser plugin binary based on os type\nif [ $machine = Linux ]\nthen\n    pluginUrl=https://github.com/vmware/purser/releases/download/$releaseVersion/purser_plugin_linux_amd64\nelif [ $machine = Mac ]\nthen\n    pluginUrl=https://github.com/vmware/purser/releases/download/$releaseVersion/purser_plugin_darwin_amd64\nelse\n    echo \"No match found for your os: $machine\"\n    echo \"Install the plugin from source code: https://github.com/vmware/purser/blob/master/README.md\"\n    exit 3  # unsuccessful shell script\nfi\nwget -q --show-progress -O purser_plugin $pluginUrl\n\n# Move th plugin yaml to one of the location specified in \n# https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/\nif [ ! -d $HOME/.kube/plugins ]\nthen\n    mkdir $HOME/.kube/plugins\nfi\necho \"Moving plugin.yaml to $HOME/.kube/plugins/\"\nmv plugin.yaml $HOME/.kube/plugins/\n\n# Change execution permissions for the binary\nchmod +x purser_plugin\n\n# Move the binary to a location which is in environment PATH variable\necho \"Moving the binary to /usr/local/bin\"\nsudo mv purser_plugin /usr/local/bin\n\necho \"Purser plugin installation Completed\"\n\necho \"\"\n\necho \"Purser Installation Completed\""
  },
  {
    "path": "build/purser-binary-uninstall.sh",
    "content": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\necho \"Removing plugin.yaml from $HOME/.kube/plugins/\"\nrm $HOME/.kube/plugins/plugin.yaml\n\necho \"Removing the binary from /usr/local/bin\"\nsudo rm /usr/local/bin/purser_plugin\n"
  },
  {
    "path": "build/purser-minimal-setup.sh",
    "content": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#    http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# Realease Version\nreleaseVersion=1.0.2\n\necho \"Installing Purser (minimal setup) version: ${releaseVersion}\"\n\n# Namespace setup\necho \"Creating namespace purser\"\nkubectl create ns purser\n\n# DB setup\necho \"Setting up database for Purser\"\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/minimal/purser-database-setup.yaml -O\nkubectl --namespace=purser create -f purser-database-setup.yaml\necho \"Waiting for database containers to be in running state... (30s)\"\nsleep 30s\n\n# Purser controller setup\necho \"Setting up controller for Purser\"\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/minimal/purser-controller-setup.yaml -O\nkubectl --namespace=purser create -f purser-controller-setup.yaml\n\n# Purser UI setup\necho \"Setting up UI for Purser\"\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/minimal/purser-ui-setup.yaml -O\nkubectl --namespace=purser create -f purser-ui-setup.yaml\n\necho \"Purser setup is completed\"\n"
  },
  {
    "path": "build/purser-setup.sh",
    "content": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#    http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# Kubeconfig location\nread -p \"Location for cluster's configuration (Press 'Enter' to take default $HOME/.kube/config): \" readConfig\nif [ -z \"$readConfig\" ];\nthen\n    kubeConfig=\"$HOME/.kube/config\"\nelse\n    kubeConfig=$readConfig\nfi\n\n# Realease Version\nreleaseVersion=1.0.2\necho \"Installing Purser version: ${releaseVersion}\"\n\n# Namespace setup\necho \"Creating namespace purser\"\nkubectl --kubeconfig=$kubeConfig create ns purser\n\n# DB setup\necho \"Setting up database for Purser\"\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-database-setup.yaml -O\nkubectl --kubeconfig=$kubeConfig --namespace=purser create -f purser-database-setup.yaml\necho \"Waiting for database containers to be in running state... (1 minute)\"\nsleep 60s\n\n# Purser controller setup\necho \"Setting up controller for Purser\"\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-controller-setup.yaml -O\nkubectl --kubeconfig=$kubeConfig --namespace=purser create -f purser-controller-setup.yaml\n\n# Purser UI setup\necho \"Setting up UI for Purser\"\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-ui-setup.yaml -O\nkubectl --kubeconfig=$kubeConfig --namespace=purser create -f purser-ui-setup.yaml\n\necho \"Purser setup is completed\"\n"
  },
  {
    "path": "cluster/artifacts/example-group.yaml",
    "content": "apiVersion: vmware.purser.com/v1\nkind: Group\nmetadata:\n  name: example-group\nspec:\n  name: example-group\n  labels:\n    expr1:\n      app:\n        - sample-app\n        - sample-app2\n      env:\n        - dev\n    expr2:\n      namespace:\n        - ns1\n        - ns2\n    expr3:\n      key1:\n        - val1\n      key2:\n        - val2"
  },
  {
    "path": "cluster/artifacts/example-subscriber.yaml",
    "content": "apiVersion: vmware.purser.com/v1\nkind: Subscriber\nmetadata:\n  name: example-subscriber\nspec:\n  name: example-subscriber\n  headers:\n    authorization: \"Bearer <your-token>\"\n    cluster: \"<your-cluster-name>\"\n  url: <your-webhook-url>"
  },
  {
    "path": "cluster/artifacts/group-template.json",
    "content": "{\n  \"apiVersion\": \"vmware.purser.com/v1\",\n  \"kind\": \"Group\",\n  \"metadata\": {\n    \"name\": \"<enter-custom-group-name>\"\n  },\n  \"spec\": {\n    \"name\": \"<enter-custom-group-name>\",\n    \"labels\": {\n      \"expr1\": {\n        \"<enter-key-1>\": [\n          \"<enter-value-1>\",\n          \"<enter-value-2>\"\n        ],\n        \"<enter-key-2>\": [\n          \"<enter-value-3>\"\n        ]\n      },\n      \"expr2\": {\n        \"<enter-key-3>\": [\n          \"<enter-value-4>\"\n        ],\n        \"<enter-key-4>\": [\n          \"<enter-value-5>\"\n        ]\n      }\n    }\n  }\n}"
  },
  {
    "path": "cluster/artifacts/purser-group-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: groups.vmware.purser.com\nspec:\n  group: vmware.purser.com\n  names:\n    kind: Group\n    listKind: GroupList\n    plural: groups\n    singular: group\n  scope: Namespaced\n  version: v1\nstatus:\n  acceptedNames:\n    kind: Group\n    listKind: GroupList\n    plural: groups\n    singular: group"
  },
  {
    "path": "cluster/artifacts/purser-subscriber-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: subscribers.vmware.purser.com\nspec:\n  group: vmware.purser.com\n  names:\n    kind: Subscriber\n    listKind: SubscriberList\n    plural: subscribers\n    singular: subscriber\n  scope: Namespaced\n  version: v1\nstatus:\n  acceptedNames:\n    kind: Subscriber\n    listKind: SubscriberList\n    plural: subscribers\n    singular: subscriber"
  },
  {
    "path": "cluster/helm/chart/purser/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\n"
  },
  {
    "path": "cluster/helm/chart/purser/Chart.yaml",
    "content": "apiVersion: v1\nappVersion: \"1.0\"\ndescription: A Helm chart for Purser \nname: purser\nversion: 0.1.0\n"
  },
  {
    "path": "cluster/helm/chart/purser/README.md",
    "content": "# [Purser](https://github.com/vmware/purser)\n\nPurser is an extension to Kubernetes tasked at providing an insight into cluster topology, costing, capacity allocations and resource interactions along with the provision of logical grouping of resources for Kubernetes based cloud native applications in a cloud neutral manner, with the focus on catering to a multitude of users ranging from Sys Admins, to DevOps to Developers.\n\nIt comprises of three components: a controller, a plugin and a UI dashboard.\n\nThe controller component deployed inside the cluster watches for K8s native and custom resources associated with the application, thereby, periodically building not just an inventory but also performing discovery by generating and storing the interactions among the resources such as containers, pods and services.\n\nThe plugin component is a CLI tool interfacing with the kubectl that helps query costs, savings defined at a level of control of the application level components rather than at the infrastructure level.\n\nThe UI dashboard is a robust application that renders the Purser UI for providing visual representation to the complete cluster metrics in a single pane of glass.\n\n> Taken from main [README](https://github.com/vmware/purser/blob/master/README.md)\n\n> [Plugin installation guide](https://github.com/vmware/purser/blob/master/README.md#purser-plugin-setup)\n\n## Chart Configuration\n\n*See `values.yaml` for configuration notes. Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,\n\n```console\n$ helm install --name purser \\\n  --set database.storage=10Gi \\\n    purser\n```\n\nAlternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example,\n\n```console\n$ helm install --name purser -f values.yaml\n```\n\n> **Tip**: You can use the default [values.yaml](values.yaml)"
  },
  {
    "path": "cluster/helm/chart/purser/templates/NOTES.txt",
    "content": "1. Get the application URL by running these commands:\n{{- if .Values.ui.ingress.enabled }}\n{{- range $host := .Values.ui.ingress.hosts }}\n  {{- range .paths }}\n  http{{ if $.Values.ui.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}\n  {{- end }}\n{{- end }}\n{{- else if contains \"NodePort\" .Values.ui.service.type }}\n  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath=\"{.spec.ports[0].nodePort}\" services {{ include \"purser.fullname\" . }}-ui)\n  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath=\"{.items[0].status.addresses[0].address}\")\n  echo http://$NODE_IP:$NODE_PORT\n{{- else if contains \"LoadBalancer\" .Values.ui.service.type }}\n     NOTE: It may take a few minutes for the LoadBalancer IP to be available.\n           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include \"purser.fullname\" . }}-ui'\n  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include \"purser.fullname\" . }}-ui -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n  echo http://$SERVICE_IP:{{ .Values.service.port }}\n{{- else if contains \"ClusterIP\" .Values.ui.service.type }}\n  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l \"app.kubernetes.io/name={{ include \"purser.name\" . }}-ui,app.kubernetes.io/instance={{ .Release.Name }}\" -o jsonpath=\"{.items[0].metadata.name}\")\n  echo \"Visit http://127.0.0.1:8080 to use your application\"\n  kubectl port-forward $POD_NAME 8080:80\n{{- end }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"purser.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"purser.fullname\" -}}\n{{- if .Values.fullnameOverride -}}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- $name := default .Chart.Name .Values.nameOverride -}}\n{{- if contains $name .Release.Name -}}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"purser.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-controller-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-controller\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-controller\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  replicas: {{ .Values.controller.replicaCount }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"purser.name\" . }}-controller\n      app.kubernetes.io/instance: {{ .Release.Name }}\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: {{ include \"purser.name\" . }}-controller\n        app.kubernetes.io/instance: {{ .Release.Name }}\n    spec:\n      serviceAccountName: {{ include \"purser.fullname\" . }}\n      containers:\n      - name: {{ .Chart.Name }}\n        image: \"{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}\"\n        imagePullPolicy: {{ .Values.controller.image.pullPolicy }}\n        command: \n        - \"/controller\"\n        args:\n        - \"--cookieKey=purser-super-secret-key\"\n        - \"--cookieName=purser-session-token\"\n        - \"--log=info\"\n        {{- if .Values.controller.interactions }}\n        - \"--interactions=enable\"\n        {{- else }}\n        - \"--interactions=disable\"\n        {{- end }}\n        - \"--dgraphURL={{ include \"purser.fullname\" . }}-database\"\n        - \"--dgraphPort=9080\"\n        ports:\n          - name: http\n            containerPort: 3030\n            protocol: TCP\n        resources:\n          {{- toYaml .Values.controller.resources | nindent 12 }}\n      initContainers:\n      - name: init-sleep\n        image: \"{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}\"\n        command: [\"/usr/bin/bash\", \"-c\", \"sleep 60\"]\n      {{- with .Values.controller.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n    {{- with .Values.controller.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n    {{- with .Values.controller.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-controller-rbac.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"purser.fullname\" . }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nrules:\n  - apiGroups: [\"apiextensions.k8s.io\"]\n    resources: [\"customresourcedefinitions\"]\n    verbs: [\"get\", \"watch\", \"list\", \"update\", \"create\", \"delete\"]\n  - apiGroups: [\"vmware.purser.com\"]\n    resources: [\"groups\", \"subscribers\"]\n    verbs: [\"get\", \"watch\", \"list\", \"update\", \"create\", \"delete\"]\n  - apiGroups: [\"*\"]\n    resources: [\"*\"]\n    verbs: [\"get\", \"watch\", \"list\"]\n{{- if .Values.controller.interaction }}\n  - apiGroups: [\"*\"]\n    resources: [\"pods/exec\"]\n    verbs: [\"create\"]\n{{- end }}\n---\n# ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ include \"purser.fullname\" . }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"purser.fullname\" . }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ include \"purser.fullname\" . }}\n    namespace: {{ .Release.Namespace }}"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-controller-serviceaccount.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"purser.fullname\" . }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-controller-svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-controller\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  type: {{ .Values.controller.service.type }}\n  ports:\n    - port: 3030\n      targetPort: http\n      protocol: TCP\n  selector:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-controller\n    app.kubernetes.io/instance: {{ .Release.Name }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-database-statefulset.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-database\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-database\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  serviceName: \"dgraph\"\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"purser.name\" . }}-database\n      app.kubernetes.io/instance: {{ .Release.Name }}\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: {{ include \"purser.name\" . }}-database\n        app.kubernetes.io/instance: {{ .Release.Name }}\n    spec:\n      containers:\n        - name: {{ .Chart.Name }}-zero\n          image: \"{{ .Values.database.zero.image.repository }}:{{ .Values.database.zero.image.tag }}\"\n          imagePullPolicy: {{ .Values.database.zero.image.pullPolicy }}\n          ports:\n          - containerPort: 5080\n            name: zero-grpc\n          - containerPort: 6080\n            name: zero-http\n          volumeMounts:\n          - name: datadir\n            mountPath: /dgraph\n          command:\n            - bash\n            - \"-c\"\n            - |\n              set -ex\n              dgraph zero --my=0.0.0.0:5080\n          resources:\n            {{- toYaml .Values.database.zero.resources | nindent 12 }}\n        - name: {{ .Chart.Name }}-server\n          image: \"{{ .Values.database.zero.image.repository }}:{{ .Values.database.zero.image.tag }}\"\n          imagePullPolicy: {{ .Values.database.zero.image.pullPolicy }}\n          ports:\n          - containerPort: 8080\n            name: server-http\n          - containerPort: 9080\n            name: server-grpc\n          volumeMounts:\n          - name: datadir\n            mountPath: /dgraph\n          command:\n            - bash\n            - \"-c\"\n            - |\n              set -ex\n              dgraph server --my=0.0.0.0:7080 --lru_mb 2048 --zero 0.0.0.0:5080\n          resources:\n            {{- toYaml .Values.database.server.resources | nindent 12 }}\n      terminationGracePeriodSeconds: 60\n      volumes:\n      - name: datadir\n        persistentVolumeClaim:\n          claimName: datadir\n      {{- with .Values.database.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n    {{- with .Values.database.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n    {{- with .Values.database.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n  volumeClaimTemplates:\n  - metadata:\n      name: datadir\n      annotations:\n        volume.alpha.kubernetes.io/storage-class: anything\n    spec:\n      accessModes:\n        - \"ReadWriteOnce\"\n      resources:\n        requests:\n          storage: {{ .Values.database.storage | default \"10Gi\" }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-database-svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-database\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  type: {{ .Values.database.service.type }}\n  ports:\n  - port: 5080\n    targetPort: 5080\n    name: zero-grpc\n  - port: 6080\n    targetPort: 6080\n    name: zero-http\n  - port: 8080\n    targetPort: 8080\n    name: server-http\n  - port: 9080\n    targetPort: 9080\n    name: server-grpc\n  selector:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-database\n    app.kubernetes.io/instance: {{ .Release.Name }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-ui-configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-ui\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-ui\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\ndata:\n  nginx.conf: |\n    upstream purser {\n        server {{ include \"purser.fullname\" . }}-controller:3030;\n    }\n    server {\n        listen 4200;\n\n        location /auth {\n            proxy_pass http://purser;\n        }\n\n        location /api {\n            proxy_pass http://purser;\n        }\n\n        location / {\n            root /usr/share/nginx/html/purser;\n            index index.html index.htm;\n            try_files $uri $uri/ /index.html =404;\n        }\n    }"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-ui-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-ui\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-ui\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  replicas: {{ .Values.ui.replicaCount }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"purser.name\" . }}-ui\n      app.kubernetes.io/instance: {{ .Release.Name }}\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: {{ include \"purser.name\" . }}-ui\n        app.kubernetes.io/instance: {{ .Release.Name }}\n    spec:\n      volumes:\n      - configMap:\n          defaultMode: 420\n          name: {{ include \"purser.fullname\" . }}-ui\n        name: nginx\n      containers:\n        - name: {{ .Chart.Name }}\n          image: \"{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag }}\"\n          imagePullPolicy: {{ .Values.ui.image.pullPolicy }}\n          ports:\n            - name: http\n              containerPort: 4200\n              protocol: TCP\n          volumeMounts:\n          - mountPath: /etc/nginx/conf.d\n            name: nginx\n          livenessProbe:\n            httpGet:\n              path: /\n              port: http\n          readinessProbe:\n            httpGet:\n              path: /\n              port: http\n          resources:\n            {{- toYaml .Values.ui.resources | nindent 12 }}\n      {{- with .Values.ui.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n    {{- with .Values.ui.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n    {{- with .Values.ui.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-ui-ingress.yaml",
    "content": "{{- if .Values.ui.ingress.enabled -}}\n{{- $fullName := include \"purser.fullname\" . -}}\napiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: {{ $fullName }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n  {{- with .Values.ui.ingress.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n{{- if .Values.ui.ingress.tls }}\n  tls:\n  {{- range .Values.ui.ingress.tls }}\n    - hosts:\n      {{- range .hosts }}\n        - {{ . | quote }}\n      {{- end }}\n      secretName: {{ .secretName }}\n  {{- end }}\n{{- end }}\n  rules:\n  {{- range .Values.ui.ingress.hosts }}\n    - host: {{ .host | quote }}\n      http:\n        paths:\n        {{- range .paths }}\n          - path: {{ . }}\n            backend:\n              serviceName: {{ $fullName }}-ui\n              servicePort: http\n        {{- end }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/templates/purser-ui-svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"purser.fullname\" . }}-ui\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-ui\n    helm.sh/chart: {{ include \"purser.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  type: {{ .Values.ui.service.type }}\n  ports:\n    - port: {{ .Values.ui.service.port }}\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    app.kubernetes.io/name: {{ include \"purser.name\" . }}-ui\n    app.kubernetes.io/instance: {{ .Release.Name }}\n"
  },
  {
    "path": "cluster/helm/chart/purser/values.yaml",
    "content": "# Default values for purser.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\nnameOverride: \"\"\nfullnameOverride: \"\"\n\ncontroller:\n  replicaCount: 1\n  interaction: false\n  image:\n    repository: kreddyj/controller-amd64\n    tag: 1.0.2\n    pullPolicy: Always\n\n  service:\n    type: ClusterIP\n    port: 80\n\n  resources: {}\n    # We usually recommend not to specify default resources and to leave this as a conscious\n    # choice for the user. This also increases chances charts run on environments with little\n    # resources, such as Minikube. If you do want to specify resources, uncomment the following\n    # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n    # limits:\n    #   cpu: 100m\n    #   memory: 128Mi\n    # requests:\n    #   cpu: 100m\n    #   memory: 128Mi\n\n  nodeSelector: {}\n\n  tolerations: []\n\n  affinity: {}\n\ndatabase:\n  replicaCount: 1\n  # Storage space given to dgraph\n  storage: 10Gi\n  service:\n    type: ClusterIP\n  \n  zero:\n    image:\n      repository: dgraph/dgraph\n      tag: v1.0.9\n      pullPolicy: IfNotPresent\n    resources: {}\n    # We usually recommend not to specify default resources and to leave this as a conscious\n    # choice for the user. This also increases chances charts run on environments with little\n    # resources, such as Minikube. If you do want to specify resources, uncomment the following\n    # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n    # limits:\n    #   cpu: 100m\n    #   memory: 128Mi\n    # requests:\n    #   cpu: 100m\n    #   memory: 128Mi\n  \n  server:\n    image:\n      repository: dgraph/dgraph\n      tag: v1.0.9\n      pullPolicy: IfNotPresent\n    resources: {}\n    # We usually recommend not to specify default resources and to leave this as a conscious\n    # choice for the user. This also increases chances charts run on environments with little\n    # resources, such as Minikube. If you do want to specify resources, uncomment the following\n    # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n    # limits:\n    #   cpu: 100m\n    #   memory: 128Mi\n    # requests:\n    #   cpu: 100m\n    #   memory: 128Mi\n\n  nodeSelector: {}\n\n  tolerations: []\n\n  affinity: {}\n\nui:\n  replicaCount: 1\n\n  image:\n    repository: kreddyj/purser-ui\n    tag: 1.0.2\n    pullPolicy: Always\n\n  service:\n    type: ClusterIP\n    port: 80\n\n  ingress:\n    enabled: false\n    annotations: {}\n      # kubernetes.io/ingress.class: nginx\n      # kubernetes.io/tls-acme: \"true\"\n    hosts:\n      - host: chart-example.local\n        paths: []\n\n    tls: []\n    #  - secretName: chart-example-tls\n    #    hosts:\n    #      - chart-example.local\n\n  resources: {}\n    # We usually recommend not to specify default resources and to leave this as a conscious\n    # choice for the user. This also increases chances charts run on environments with little\n    # resources, such as Minikube. If you do want to specify resources, uncomment the following\n    # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n    # limits:\n    #   cpu: 100m\n    #   memory: 128Mi\n    # requests:\n    #   cpu: 100m\n    #   memory: 128Mi\n\n  nodeSelector: {}\n\n  tolerations: []\n\n  affinity: {}"
  },
  {
    "path": "cluster/minimal/purser-controller-setup.yaml",
    "content": "# Service account\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: purser-service-account\n---\n# RBAC\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRole\nmetadata:\n  name: purser-permissions\nrules:\n  - apiGroups: [\"apiextensions.k8s.io\"]\n    resources: [\"customresourcedefinitions\"]\n    verbs: [\"get\", \"watch\", \"list\", \"update\", \"create\", \"delete\"]\n  - apiGroups: [\"vmware.purser.com\"]\n    resources: [\"groups\", \"subscribers\"]\n    verbs: [\"get\", \"watch\", \"list\", \"update\", \"create\", \"delete\"]\n  - apiGroups: [\"*\"]\n    resources: [\"*\"]\n    verbs: [\"get\", \"watch\", \"list\"]\n# Uncomment next three lines to enable interactions feature.\n#  - apiGroups: [\"*\"]\n#    resources: [\"pods/exec\"]\n#    verbs: [\"create\"]\n---\n# ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRoleBinding\nmetadata:\n  name: purser-cluster-role\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: purser-permissions\nsubjects:\n  - kind: ServiceAccount\n    name: purser-service-account\n    namespace: purser\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: purser\nspec:\n  selector:\n    app: purser\n  ports:\n  - protocol: TCP\n    port: 3030\n    targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: purser\nspec:\n  selector:\n    matchLabels:\n        app: purser\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: purser\n    spec:\n      serviceAccountName: purser-service-account\n      containers:\n        - name: purser-controller\n          image: kreddyj/purser:controller-1.0.2\n          imagePullPolicy: Always\n          ports:\n            - name: http\n              containerPort: 3030\n          command: [\"/controller\"]\n          args: [\"--log=info\", \"--interactions=disable\", \"--dgraphURL=purser-db\", \"--dgraphPort=9080\"]\n"
  },
  {
    "path": "cluster/minimal/purser-database-setup.yaml",
    "content": "# Service Dgraph - This is the service that should be used by the clients of Dgraph to talk to the server.\napiVersion: v1\nkind: Service\nmetadata:\n  name: purser-db\n  labels:\n    app: purser-db\nspec:\n  type: ClusterIP\n  ports:\n  - port: 5080\n    targetPort: 5080\n    name: zero-grpc\n  - port: 6080\n    targetPort: 6080\n    name: zero-http\n  - port: 8080\n    targetPort: 8080\n    name: server-http\n  - port: 9080\n    targetPort: 9080\n    name: server-grpc\n  selector:\n    app: purser-db\n---\n# Dgraph StatefulSet runs 1 pod with one Zero and one Server containers.\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: purser-dgraph\nspec:\n  serviceName: \"dgraph\"\n  replicas: 1\n  selector:\n      matchLabels:\n        app: purser-db\n  template:\n    metadata:\n      labels:\n        app: purser-db\n    spec:\n      containers:\n      - name: zero\n        image: dgraph/dgraph:v1.0.9\n        imagePullPolicy: IfNotPresent\n        ports:\n        - containerPort: 5080\n          name: zero-grpc\n        - containerPort: 6080\n          name: zero-http\n        volumeMounts:\n        - name: datadir\n          mountPath: /dgraph\n        command:\n          - bash\n          - \"-c\"\n          - |\n            set -ex\n            dgraph zero --my=$(hostname -f):5080\n      - name: server\n        image: dgraph/dgraph:v1.0.9\n        imagePullPolicy: IfNotPresent\n        ports:\n        - containerPort: 8080\n          name: server-http\n        - containerPort: 9080\n          name: server-grpc\n        volumeMounts:\n        - name: datadir\n          mountPath: /dgraph\n        command:\n          - bash\n          - \"-c\"\n          - |\n            set -ex\n            dgraph server --my=$(hostname -f):7080 --lru_mb 2048 --zero $(hostname -f):5080\n      terminationGracePeriodSeconds: 60\n      volumes:\n      - name: datadir\n        persistentVolumeClaim:\n          claimName: datadir\n  updateStrategy:\n    type: RollingUpdate\n  volumeClaimTemplates:\n  - metadata:\n      name: datadir\n      annotations:\n        volume.alpha.kubernetes.io/storage-class: anything\n    spec:\n      accessModes:\n        - \"ReadWriteOnce\"\n      resources:\n        requests:\n          storage: 5Gi\n"
  },
  {
    "path": "cluster/minimal/purser-ui-setup.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: purser-ui\n  labels:\n    run: purser-ui\n    app: purser\nspec:\n  selector:\n    app: purser\n    run: purser-ui\n  ports:\n  - protocol: TCP\n    port: 80\n    targetPort: 4200\n  type: LoadBalancer\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: purser-ui\nspec:\n  selector:\n    matchLabels:\n      app: purser\n      run: purser-ui\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: purser\n        run: purser-ui\n    spec:\n      containers:\n      - name: purser-ui\n        image: kreddyj/purser:ui-1.0.2\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 4200\n"
  },
  {
    "path": "cluster/purser-controller-setup.yaml",
    "content": "# Service account\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: purser-service-account\n---\n# RBAC\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRole\nmetadata:\n  name: purser-permissions\nrules:\n  - apiGroups: [\"apiextensions.k8s.io\"]\n    resources: [\"customresourcedefinitions\"]\n    verbs: [\"get\", \"watch\", \"list\", \"update\", \"create\", \"delete\"]\n  - apiGroups: [\"vmware.purser.com\"]\n    resources: [\"groups\", \"subscribers\"]\n    verbs: [\"get\", \"watch\", \"list\", \"update\", \"create\", \"delete\"]\n  - apiGroups: [\"*\"]\n    resources: [\"*\"]\n    verbs: [\"get\", \"watch\", \"list\"]\n# Uncomment next three lines to enable interactions feature.\n#  - apiGroups: [\"*\"]\n#    resources: [\"pods/exec\"]\n#    verbs: [\"create\"]\n---\n# ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRoleBinding\nmetadata:\n  name: purser-cluster-role\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: purser-permissions\nsubjects:\n  - kind: ServiceAccount\n    name: purser-service-account\n    namespace: purser\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: purser\nspec:\n  selector:\n    app: purser\n  ports:\n  - protocol: TCP\n    port: 3030\n    targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: purser\nspec:\n  selector:\n    matchLabels:\n        app: purser\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: purser\n    spec:\n      serviceAccountName: purser-service-account\n      containers:\n        - name: purser-controller\n          image: kreddyj/purser:controller-1.0.2\n          imagePullPolicy: Always\n          resources:\n            limits:\n              memory: 1000Mi\n              cpu: 300m\n            requests:\n              memory: 1000Mi\n              cpu: 300m\n          ports:\n            - name: http\n              containerPort: 3030\n          command: [\"/controller\"]\n          args: [\"--log=info\", \"--interactions=disable\", \"--dgraphURL=purser-db\", \"--dgraphPort=9080\"]\n"
  },
  {
    "path": "cluster/purser-database-setup.yaml",
    "content": "# Service Dgraph - This is the service that should be used by the clients of Dgraph to talk to the server.\napiVersion: v1\nkind: Service\nmetadata:\n  name: purser-db\n  labels:\n    app: purser-db\nspec:\n  type: ClusterIP\n  ports:\n  - port: 5080\n    targetPort: 5080\n    name: zero-grpc\n  - port: 6080\n    targetPort: 6080\n    name: zero-http\n  - port: 8080\n    targetPort: 8080\n    name: server-http\n  - port: 9080\n    targetPort: 9080\n    name: server-grpc\n  selector:\n    app: purser-db\n---\n# Dgraph StatefulSet runs 1 pod with one Zero and one Server containers.\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: purser-dgraph\nspec:\n  serviceName: \"dgraph\"\n  replicas: 1\n  selector:\n      matchLabels:\n        app: purser-db\n  template:\n    metadata:\n      labels:\n        app: purser-db\n    spec:\n      containers:\n      - name: zero\n        image: dgraph/dgraph:v1.0.9\n        imagePullPolicy: IfNotPresent\n        resources:\n          limits:\n            memory: 1000Mi\n            cpu: 300m\n          requests:\n            memory: 1000Mi\n            cpu: 300m\n        ports:\n        - containerPort: 5080\n          name: zero-grpc\n        - containerPort: 6080\n          name: zero-http\n        volumeMounts:\n        - name: datadir\n          mountPath: /dgraph\n        command:\n          - bash\n          - \"-c\"\n          - |\n            set -ex\n            dgraph zero --my=$(hostname -f):5080\n      - name: server\n        image: dgraph/dgraph:v1.0.9\n        imagePullPolicy: IfNotPresent\n        resources:\n          limits:\n            memory: 1500Mi\n            cpu: 500m\n          requests:\n            memory: 1500Mi\n            cpu: 500m\n        ports:\n        - containerPort: 8080\n          name: server-http\n        - containerPort: 9080\n          name: server-grpc\n        volumeMounts:\n        - name: datadir\n          mountPath: /dgraph\n        command:\n          - bash\n          - \"-c\"\n          - |\n            set -ex\n            dgraph server --my=$(hostname -f):7080 --lru_mb 2048 --zero $(hostname -f):5080\n      terminationGracePeriodSeconds: 60\n      volumes:\n      - name: datadir\n        persistentVolumeClaim:\n          claimName: datadir\n  updateStrategy:\n    type: RollingUpdate\n  volumeClaimTemplates:\n  - metadata:\n      name: datadir\n      annotations:\n        volume.alpha.kubernetes.io/storage-class: anything\n    spec:\n      accessModes:\n        - \"ReadWriteOnce\"\n      resources:\n        requests:\n          storage: 10Gi\n"
  },
  {
    "path": "cluster/purser-ui-setup.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: purser-ui\n  labels:\n    run: purser-ui\n    app: purser\nspec:\n  selector:\n    app: purser\n    run: purser-ui\n  ports:\n  - protocol: TCP\n    port: 80\n    targetPort: 4200\n  type: LoadBalancer\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: purser-ui\nspec:\n  selector:\n    matchLabels:\n      app: purser\n      run: purser-ui\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: purser\n        run: purser-ui\n    spec:\n      containers:\n      - name: purser-ui\n        image: kreddyj/purser:ui-1.0.2\n        imagePullPolicy: Always\n        resources:\n          limits:\n            memory: 1200Mi\n            cpu: 500m\n          requests:\n            memory: 1200Mi\n            cpu: 500m\n        ports:\n        - containerPort: 4200"
  },
  {
    "path": "cmd/controller/api/api.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage api\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/gorilla/handlers\"\n\t\"github.com/vmware/purser/cmd/controller/api/apiHandlers\"\n\t\"github.com/vmware/purser/pkg/controller\"\n)\n\n// StartServer starts api server\nfunc StartServer(conf controller.Config) {\n\tapiHandlers.SetKubeClientAndGroupClient(conf)\n\tallowedOrigins := handlers.AllowedOrigins([]string{\"*\"})\n\tallowedCredentials := handlers.AllowCredentials()\n\trouter := NewRouter()\n\tlogrus.Info(\"Purser server started on port `localhost:3030`\")\n\tlogrus.Fatal(http.ListenAndServe(\":3030\", handlers.CORS(allowedOrigins, allowedCredentials)(router)))\n}\n"
  },
  {
    "path": "cmd/controller/api/apiHandlers/authenticationHandlers.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apiHandlers\n\nimport (\n\t\"encoding/json\"\n\t\"encoding/gob\"\n\t\"net/http\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/gorilla/sessions\"\n\t\"github.com/gorilla/securecookie\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n)\n\n// Credentials structure\ntype Credentials struct {\n\tPassword    string `json:\"password\"`\n\tUsername    string `json:\"username\"`\n\tNewPassword string `json:\"newPassword\"`\n}\n\n// User structure\ntype User struct {\n\tUsername string\n\tAuthenticated bool\n}\n\nvar cookieName = \"purser-session-token\"\n\nvar store *sessions.CookieStore\n\n// initialises cookie store\nfunc init() {\n\tauthKeyOne := securecookie.GenerateRandomKey(64)\n\tencryptionKeyOne := securecookie.GenerateRandomKey(32)\n\n\tstore = sessions.NewCookieStore(\n\t\tauthKeyOne,\n\t\tencryptionKeyOne,\n\t)\n\n\tstore.Options = &sessions.Options{\n\t\tMaxAge:   60 * 15,\n\t\tHttpOnly: true,\n\t}\n\tgob.Register(User{})\n}\n\n// LoginUser listens on /auth/login endpoint\nfunc LoginUser(w http.ResponseWriter, r *http.Request) {\n\taddAccessControlHeaders(&w, r)\n\tvar cred Credentials\n\terr := json.NewDecoder(r.Body).Decode(&cred)\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif !query.Authenticate(cred.Username, cred.Password) {\n\t\tlogrus.Errorf(\"wrong credentials\")\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tsession, err := store.Get(r, cookieName)\n\tif err != nil {\n\t\tlogrus.Errorf(\"unable to get session from cookie store, err: %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tsession.Values[\"user\"] = User{\n\t\tUsername: cred.Username,\n\t\tAuthenticated: true,\n\t}\n\n\terr = session.Save(r, w)\n\tif err != nil {\n\t\tlogrus.Errorf(\"unable to get session from cookie store, err: %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tlogrus.Infof(\"login success\")\n\tw.WriteHeader(http.StatusOK)\n}\n\n// LogoutUser listens on /auth/logout endpoint\nfunc LogoutUser(w http.ResponseWriter, r *http.Request) {\n\taddAccessControlHeaders(&w, r)\n\tsession, err := store.Get(r, cookieName)\n\tif err != nil {\n\t\tlogrus.Errorf(\"unable to get session from cookie store, err: %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tsession.Values[\"user\"] = User{}\n\tsession.Options.MaxAge = -1\n\n\terr = session.Save(r, w)\n\tif err != nil {\n\t\tlogrus.Errorf(\"unable to get session from cookie store, err: %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\thttp.Redirect(w, r, \"/\", http.StatusFound)\n}\n\n// TODO: Enhance\nfunc isUserAuthenticated(w http.ResponseWriter, r *http.Request) bool {\n\treturn true\n}\n\n// ChangePassword listens on /auth/changePassword endpoint\nfunc ChangePassword(w http.ResponseWriter, r *http.Request) {\n\taddAccessControlHeaders(&w, r)\n\tvar cred Credentials\n\terr := json.NewDecoder(r.Body).Decode(&cred)\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif !query.UpdatePassword(cred.Username, cred.Password, cred.NewPassword) {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "cmd/controller/api/apiHandlers/customGroupHandlers.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apiHandlers\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/Sirupsen/logrus\"\n\tgroup_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n\t\"github.com/vmware/purser/pkg/controller/eventprocessor\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"net/http\"\n)\n\n// GetGroupsData listens on /api/groups endpoint\nfunc GetGroupsData(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\n\t\tgroupsData, err := query.RetrieveGroupsData()\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"unable to retrieve groups data from dgraph, %v\", err)\n\t\t} else {\n\t\t\tencodeAndWrite(w, groupsData)\n\t\t}\n\t}\n}\n\n// DeleteGroup listens on /api/group/delete\nfunc DeleteGroup(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddAccessControlHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\t\tvar err error\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\terr = getGroupClient().Delete(name[0], &meta_v1.DeleteOptions{})\n\t\t\tif err == nil {\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\tmodels.DeleteGroup(name[0])\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tlogrus.Errorf(\"unable to delete: query params: %v, err: %v\", queryParams, err)\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t}\n}\n\n// CreateGroup listens on /api/group/create\nfunc CreateGroup(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddAccessControlHeaders(&w, r)\n\t\tgroupData, err := convertRequestBodyToJSON(r)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"unable to parse request as either JSON or YAML, err: %v\", err)\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tnewGroup := group_v1.Group{}\n\t\tif jsonErr := json.Unmarshal(groupData, &newGroup); jsonErr != nil {\n\t\t\tlogrus.Errorf(\"unable to parse object as group, err: %v\", jsonErr)\n\t\t\thttp.Error(w, jsonErr.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif _, groupErr := getGroupClient().Create(&newGroup); groupErr != nil {\n\t\t\tlogrus.Errorf(\"unable to create group: %v\", groupErr)\n\t\t\thttp.Error(w, groupErr.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t\teventprocessor.UpdateGroup(&newGroup, getGroupClient())\n\t}\n}\n"
  },
  {
    "path": "cmd/controller/api/apiHandlers/helpers.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apiHandlers\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/Sirupsen/logrus\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"net/http\"\n\t\"github.com/vmware/purser/pkg/controller\"\n\t\"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nvar groupClient *v1.GroupClient\nvar kubeClient *kubernetes.Clientset\n\nfunc addHeaders(w *http.ResponseWriter, r *http.Request) {\n\taddAccessControlHeaders(w, r)\n\t(*w).Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\n\t(*w).WriteHeader(http.StatusOK)\n}\n\nfunc addAccessControlHeaders(w *http.ResponseWriter, r *http.Request) {\n\t(*w).Header().Set(\"Access-Control-Allow-Origin\", r.Header.Get(\"Origin\"))\n\t(*w).Header().Set(\"Access-Control-Allow-Credentials\", \"true\")\n}\n\nfunc writeBytes(w io.Writer, data []byte) {\n\t_, err := w.Write(data)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to encode to json: (%v)\", err)\n\t}\n}\n\nfunc encodeAndWrite(w io.Writer, obj interface{}) {\n\terr := json.NewEncoder(w).Encode(obj)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to encode to json: (%v)\", err)\n\t}\n}\n\nfunc convertRequestBodyToJSON(r *http.Request) ([]byte, error) {\n\trequestData, err := ioutil.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgroupData, err := yaml.ToJSON(requestData)\n\treturn groupData, err\n}\n\n// SetKubeClientAndGroupClient sets groupcrd client\nfunc SetKubeClientAndGroupClient(conf controller.Config) {\n\tgroupClient = conf.Groupcrdclient\n\tkubeClient = conf.Kubeclient\n}\n\nfunc getGroupClient() *v1.GroupClient {\n\treturn groupClient\n}\n\nfunc getKubeClient() *kubernetes.Clientset {\n\treturn kubeClient\n}\n"
  },
  {
    "path": "cmd/controller/api/apiHandlers/hierarchyAndMetricAPIHandlers.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apiHandlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n\t\"github.com/vmware/purser/pkg/controller/discovery/generator\"\n\t\"github.com/vmware/purser/pkg/controller/eventprocessor\"\n)\n\n// GetHomePage is the default api home page\nfunc GetHomePage(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\t_, err := fmt.Fprintf(w, \"Welcome to the Purser!\")\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"Unable to write welcome message to Homepage: (%v)\", err)\n\t\t}\n\t}\n}\n\n// GetPodInteractions listens on /interactions/pod endpoint and returns pod interactions\nfunc GetPodInteractions(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonResp []byte\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tjsonResp = query.RetrievePodsInteractions(name[0], false)\n\t\t} else {\n\t\t\tif orphanVal, isOrphan := queryParams[query.Orphan]; isOrphan && orphanVal[0] == query.False {\n\t\t\t\tjsonResp = query.RetrievePodsInteractions(query.All, false)\n\t\t\t} else {\n\t\t\t\tjsonResp = query.RetrievePodsInteractions(query.All, true)\n\t\t\t}\n\t\t}\n\t\twriteBytes(w, jsonResp)\n\t}\n}\n\n// GetClusterHierarchy listens on /hierarchy endpoint and returns all namespaces(or nodes and PV) in the cluster\nfunc GetClusterHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif view, isView := queryParams[query.View]; isView && view[0] == query.Physical {\n\t\t\tjsonData = query.RetrieveClusterHierarchy(query.Physical)\n\t\t} else {\n\t\t\tjsonData = query.RetrieveClusterHierarchy(query.Logical)\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetNamespaceHierarchy listens on /hierarchy/namespace endpoint and returns all children of namespace\nfunc GetNamespaceHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.NamespaceCheck,\n\t\t\t\tType:        query.NamespaceType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.NamespaceChildFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tjsonData = query.RetrieveClusterHierarchy(query.Logical)\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetDeploymentHierarchy listens on /hierarchy/deployment endpoint and returns all children of deployment\nfunc GetDeploymentHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.DeploymentCheck,\n\t\t\t\tType:        query.DeploymentType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsReplicasetFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for deployment, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetReplicasetHierarchy listens on /hierarchy/replicaset endpoint and returns all children of replicaset\nfunc GetReplicasetHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.ReplicasetCheck,\n\t\t\t\tType:        query.ReplicasetType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsPodFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for replicaset, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetStatefulsetHierarchy listens on /hierarchy/statefulset endpoint and returns all children of statefulset\nfunc GetStatefulsetHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.StatefulsetCheck,\n\t\t\t\tType:        query.StatefulsetType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsPodFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for statefulset, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetPodHierarchy listens on /hierarchy/pod endpoint and returns all children of pod\nfunc GetPodHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.PodCheck,\n\t\t\t\tType:        query.PodType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsContainerFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for pod, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetContainerHierarchy listens on /hierarchy/container endpoint and returns all children of container\nfunc GetContainerHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.ContainerCheck,\n\t\t\t\tType:        query.ContainerType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsProcFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for container, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetEmptyHierarchy listens on /hierarchy/process and /hierarchy/pvc endpoint and returns empty data\nfunc GetEmptyHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetNodeHierarchy listens on /hierarchy/node endpoint and returns all children of node\nfunc GetNodeHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.NodeCheck,\n\t\t\t\tType:        query.NodeType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsPodFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for node, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetPVHierarchy listens on /hierarchy/pv endpoint and returns all children of PV\nfunc GetPVHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.PVCheck,\n\t\t\t\tType:        query.PVType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsPVCFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for PV, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetDaemonsetHierarchy listens on /hierarchy/daemonset endpoint and returns all children of Daemonset\nfunc GetDaemonsetHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.DaemonsetCheck,\n\t\t\t\tType:        query.DaemonsetType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsPodFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for Daemonset, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetJobHierarchy listens on /hierarchy/job endpoint and returns all children of Job\nfunc GetJobHierarchy(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck:       query.JobCheck,\n\t\t\t\tType:        query.JobType,\n\t\t\t\tName:        name[0],\n\t\t\t\tChildFilter: query.IsPodFilter,\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceHierarchy()\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for Job, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetClusterMetrics listens on /metrics endpoint with option for view(physical or logical)\nfunc GetClusterMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif view, isView := queryParams[query.View]; isView && view[0] == query.Physical {\n\t\t\tjsonData = query.RetrieveClusterMetrics(query.Physical)\n\t\t} else {\n\t\t\tjsonData = query.RetrieveClusterMetrics(query.Logical)\n\t\t}\n\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetNamespaceMetrics listens on /metrics/namespace\nfunc GetNamespaceMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.NamespaceCheck,\n\t\t\t\tType:  query.NamespaceType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t} else {\n\t\t\tjsonData = query.RetrieveClusterMetrics(query.Logical)\n\t\t}\n\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetDeploymentMetrics listens on /metrics/deployment\nfunc GetDeploymentMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.DeploymentCheck,\n\t\t\t\tType:  query.DeploymentType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for deployment, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetDaemonsetMetrics listens on /metrics/daemonset\nfunc GetDaemonsetMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.DaemonsetCheck,\n\t\t\t\tType:  query.DaemonsetType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for daemonset, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetJobMetrics listens on /metrics/job\nfunc GetJobMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.JobCheck,\n\t\t\t\tType:  query.JobType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for job, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetStatefulsetMetrics listens on /metrics/statefulset\nfunc GetStatefulsetMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.StatefulsetCheck,\n\t\t\t\tType:  query.StatefulsetType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for statefulset, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetReplicasetMetrics listens on /metrics/replicaset\nfunc GetReplicasetMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.ReplicasetCheck,\n\t\t\t\tType:  query.ReplicasetType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for statefulset, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetNodeMetrics listens on /metrics/node\nfunc GetNodeMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.NodeCheck,\n\t\t\t\tType:  query.NodeType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tresourceQuery.PopulateNodeOrPVAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for node, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetPodMetrics listens on /metrics/pod\nfunc GetPodMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.PodCheck,\n\t\t\t\tType:  query.PodType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for pod, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetContainerMetrics listens on /metrics/container\nfunc GetContainerMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.ContainerCheck,\n\t\t\t\tType:  query.ContainerType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for container, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetPVMetrics listens on /metrics/pv\nfunc GetPVMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.PVCheck,\n\t\t\t\tType:  query.PVType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tresourceQuery.PopulateNodeOrPVAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for PV, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetPVCMetrics listens on /metrics/pvc\nfunc GetPVCMetrics(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\taddHeaders(&w, r)\n\t\tqueryParams := r.URL.Query()\n\t\tlogrus.Debugf(\"Query params: (%v)\", queryParams)\n\n\t\tvar jsonData query.JSONDataWrapper\n\t\tif name, isName := queryParams[query.Name]; isName {\n\t\t\tresourceQuery := query.Resource{\n\t\t\t\tCheck: query.PVCCheck,\n\t\t\t\tType:  query.PVCType,\n\t\t\t\tName:  name[0],\n\t\t\t}\n\t\t\tjsonData = resourceQuery.RetrieveResourceMetrics()\n\t\t\tquery.PopulateClusterAllocationAndCapacity(&jsonData)\n\t\t} else {\n\t\t\tlogrus.Errorf(\"wrong type of query for PVC, no name is given\")\n\t\t}\n\t\tencodeAndWrite(w, jsonData)\n\t}\n}\n\n// GetPodDiscoveryNodes listens on /discovery/pod/nodes endpoint\nfunc GetPodDiscoveryNodes(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\tvar pods []models.Pod\n\t\tvar err error\n\n\t\taddHeaders(&w, r)\n\t\tpods, err = query.RetrievePodsInteractionsForAllLivePodsWithCount()\n\t\tgenerator.GeneratePodNodesAndEdges(pods)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"Unable to get response: (%v)\", err)\n\t\t}\n\t\tnodes := generator.GetGraphNodes()\n\t\tif nodes != nil {\n\t\t\tlogrus.Infof(\"No nodes found\")\n\t\t\treturn\n\t\t}\n\t\terr = json.NewEncoder(w).Encode(nodes)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"Unable to encode to json: (%v)\", err)\n\t\t}\n\t}\n}\n\n// GetPodDiscoveryEdges listens on /discovery/pod/edges endpoint\nfunc GetPodDiscoveryEdges(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\tvar err error\n\t\taddHeaders(&w, r)\n\n\t\tedges := generator.GetGraphEdges()\n\t\tif edges == nil {\n\t\t\tlogrus.Infof(\"No edges found\")\n\t\t\treturn\n\t\t}\n\t\terr = json.NewEncoder(w).Encode(edges)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"Unable to encode to json: (%v)\", err)\n\t\t}\n\t}\n}\n\n// SyncCluster listens on /api/sync\nfunc SyncCluster(w http.ResponseWriter, r *http.Request) {\n\tif isUserAuthenticated(w, r) {\n\t\tw.WriteHeader(http.StatusAccepted)\n\t\tgo syncResourcesInCluster()\n\t}\n}\n\nfunc syncResourcesInCluster() {\n\teventprocessor.SyncCluster(getKubeClient())\n\teventprocessor.UpdateGroups(getGroupClient())\n\tquery.ComputeClusterAllocationAndCapacity()\n}\n"
  },
  {
    "path": "cmd/controller/api/logger.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage api\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/Sirupsen/logrus\"\n)\n\n// Logger implements web logging logic\nfunc Logger(inner http.Handler, name string) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tstart := time.Now()\n\t\tinner.ServeHTTP(w, r)\n\t\tlogrus.Infof(\n\t\t\t\"%s\\t%s\\t%s\\t%s\",\n\t\t\tr.Method,\n\t\t\tr.RequestURI,\n\t\t\tname,\n\t\t\ttime.Since(start),\n\t\t)\n\t})\n}\n"
  },
  {
    "path": "cmd/controller/api/router.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage api\n\nimport (\n\t\"github.com/gorilla/mux\"\n)\n\n// NewRouter returns a new instance of the router\nfunc NewRouter() *mux.Router {\n\trouter := mux.NewRouter().StrictSlash(true)\n\tfor _, route := range routes {\n\t\thandlerFunc := route.HandlerFunc\n\t\thandler := Logger(handlerFunc, route.Name)\n\n\t\trouter.\n\t\t\tMethods(route.Method).\n\t\t\tPath(route.Pattern).\n\t\t\tName(route.Name).\n\t\t\tHandler(handler)\n\t}\n\treturn router\n}\n"
  },
  {
    "path": "cmd/controller/api/routes.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage api\n\nimport (\n\t\"github.com/vmware/purser/cmd/controller/api/apiHandlers\"\n\t\"net/http\"\n)\n\n// Route structure\ntype Route struct {\n\tName        string\n\tMethod      string\n\tPattern     string\n\tHandlerFunc http.HandlerFunc\n}\n\n// Routes list\ntype Routes []Route\n\nvar routes = Routes{\n\tRoute{\n\t\t\"GetHomePage\",\n\t\t\"GET\",\n\t\t\"/api\",\n\t\tapiHandlers.GetHomePage,\n\t},\n\tRoute{\n\t\t\"GetPodInteractions\",\n\t\t\"GET\",\n\t\t\"/api/interactions/pod\",\n\t\tapiHandlers.GetPodInteractions,\n\t},\n\tRoute{\n\t\t\"GetClusterHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy\",\n\t\tapiHandlers.GetClusterHierarchy,\n\t},\n\tRoute{\n\t\t\"GetNamespaceHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/namespace\",\n\t\tapiHandlers.GetNamespaceHierarchy,\n\t},\n\tRoute{\n\t\t\"GetDeploymentHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/deployment\",\n\t\tapiHandlers.GetDeploymentHierarchy,\n\t},\n\tRoute{\n\t\t\"GetReplicasetHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/replicaset\",\n\t\tapiHandlers.GetReplicasetHierarchy,\n\t},\n\tRoute{\n\t\t\"GetStatefulsetHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/statefulset\",\n\t\tapiHandlers.GetStatefulsetHierarchy,\n\t},\n\tRoute{\n\t\t\"GetPodHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/pod\",\n\t\tapiHandlers.GetPodHierarchy,\n\t},\n\tRoute{\n\t\t\"GetContainerHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/container\",\n\t\tapiHandlers.GetContainerHierarchy,\n\t},\n\tRoute{\n\t\t\"GetProcessHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/process\",\n\t\tapiHandlers.GetEmptyHierarchy,\n\t},\n\tRoute{\n\t\t\"GetNodeHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/node\",\n\t\tapiHandlers.GetNodeHierarchy,\n\t},\n\tRoute{\n\t\t\"GetPVHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/pv\",\n\t\tapiHandlers.GetPVHierarchy,\n\t},\n\tRoute{\n\t\t\"GetPVCHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/pvc\",\n\t\tapiHandlers.GetEmptyHierarchy,\n\t},\n\tRoute{\n\t\t\"GetDaemonsetHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/daemonset\",\n\t\tapiHandlers.GetDaemonsetHierarchy,\n\t},\n\tRoute{\n\t\t\"GetJobHierarchy\",\n\t\t\"GET\",\n\t\t\"/api/hierarchy/job\",\n\t\tapiHandlers.GetJobHierarchy,\n\t},\n\tRoute{\n\t\t\"GetClusterMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics\",\n\t\tapiHandlers.GetClusterMetrics,\n\t},\n\tRoute{\n\t\t\"GetNamespaceMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/namespace\",\n\t\tapiHandlers.GetNamespaceMetrics,\n\t},\n\tRoute{\n\t\t\"GetDeploymentMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/deployment\",\n\t\tapiHandlers.GetDeploymentMetrics,\n\t},\n\tRoute{\n\t\t\"GetDaemonsetMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/daemonset\",\n\t\tapiHandlers.GetDaemonsetMetrics,\n\t},\n\tRoute{\n\t\t\"GetJobMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/job\",\n\t\tapiHandlers.GetJobMetrics,\n\t},\n\tRoute{\n\t\t\"GetStatefulsetMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/statefulset\",\n\t\tapiHandlers.GetStatefulsetMetrics,\n\t},\n\tRoute{\n\t\t\"GetReplicasetMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/replicaset\",\n\t\tapiHandlers.GetReplicasetMetrics,\n\t},\n\tRoute{\n\t\t\"GetNodeMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/node\",\n\t\tapiHandlers.GetNodeMetrics,\n\t},\n\tRoute{\n\t\t\"GetPodMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/pod\",\n\t\tapiHandlers.GetPodMetrics,\n\t},\n\tRoute{\n\t\t\"GetContainerMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/container\",\n\t\tapiHandlers.GetContainerMetrics,\n\t},\n\tRoute{\n\t\t\"GetPVMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/pv\",\n\t\tapiHandlers.GetPVMetrics,\n\t},\n\tRoute{\n\t\t\"GetPVCMetrics\",\n\t\t\"GET\",\n\t\t\"/api/metrics/pvc\",\n\t\tapiHandlers.GetPVCMetrics,\n\t},\n\tRoute{\n\t\t\"GetPodDiscoveryNodes\",\n\t\t\"GET\",\n\t\t\"/api/nodes\",\n\t\tapiHandlers.GetPodDiscoveryNodes,\n\t},\n\tRoute{\n\t\t\"GetPodDiscoveryEdges\",\n\t\t\"GET\",\n\t\t\"/api/edges\",\n\t\tapiHandlers.GetPodDiscoveryEdges,\n\t},\n\tRoute{\n\t\t\"GetGroupsData\",\n\t\t\"GET\",\n\t\t\"/api/groups\",\n\t\tapiHandlers.GetGroupsData,\n\t},\n\tRoute{\n\t\t\"Login\",\n\t\t\"POST\",\n\t\t\"/auth/login\",\n\t\tapiHandlers.LoginUser,\n\t},\n\tRoute{\n\t\t\"Logout\",\n\t\t\"POST\",\n\t\t\"/auth/logout\",\n\t\tapiHandlers.LogoutUser,\n\t},\n\tRoute{\n\t\t\"ChangePassword\",\n\t\t\"POST\",\n\t\t\"/auth/changePassword\",\n\t\tapiHandlers.ChangePassword,\n\t},\n\tRoute{\n\t\t\"DeleteGroup\",\n\t\t\"POST\",\n\t\t\"/api/group/delete\",\n\t\tapiHandlers.DeleteGroup,\n\t},\n\tRoute{\n\t\t\"CreateGroup\",\n\t\t\"POST\",\n\t\t\"/api/group/create\",\n\t\tapiHandlers.CreateGroup,\n\t},\n\tRoute{\n\t\t\"SyncCluster\",\n\t\t\"GET\",\n\t\t\"/api/sync\",\n\t\tapiHandlers.SyncCluster,\n\t},\n}\n"
  },
  {
    "path": "cmd/controller/config/config.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"sync\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/vmware/purser/pkg/client\"\n\tgroup_client \"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n\tsubscriber_client \"github.com/vmware/purser/pkg/client/clientset/typed/subscriber/v1\"\n\t\"github.com/vmware/purser/pkg/controller\"\n\t\"github.com/vmware/purser/pkg/controller/buffering\"\n\t\"github.com/vmware/purser/pkg/utils\"\n)\n\n// Setup initialzes the controller configuration\nfunc Setup(conf *controller.Config, kubeconfig string) {\n\tvar err error\n\t*conf = controller.Config{}\n\tconf.KubeConfig, err = utils.GetKubeconfig(kubeconfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tconf.Kubeclient = utils.GetKubeclient(conf.KubeConfig)\n\tconf.Resource = controller.Resource{\n\t\tPod:                   true,\n\t\tNode:                  true,\n\t\tPersistentVolume:      true,\n\t\tPersistentVolumeClaim: true,\n\t\tReplicaSet:            true,\n\t\tDeployment:            true,\n\t\tStatefulSet:           true,\n\t\tDaemonSet:             true,\n\t\tJob:                   true,\n\t\tService:               true,\n\t\tNamespace:             true,\n\t\tGroup:                 true,\n\t\tSubscriber:            true,\n\t}\n\tconf.RingBuffer = &buffering.RingBuffer{Size: buffering.BufferSize, Mutex: &sync.Mutex{}}\n\tclientset, clusterConfig := client.GetAPIExtensionClient(kubeconfig)\n\tconf.Groupcrdclient = group_client.NewGroupClient(clientset, clusterConfig)\n\tconf.Subscriberclient = subscriber_client.NewSubscriberClient(clientset, clusterConfig)\n}\n"
  },
  {
    "path": "cmd/controller/purserctrl.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"time\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n\n\t\"github.com/vmware/purser/pkg/pricing\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/robfig/cron\"\n\t\"github.com/vmware/purser/cmd/controller/api\"\n\t\"github.com/vmware/purser/cmd/controller/config\"\n\t\"github.com/vmware/purser/pkg/controller\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/discovery/processor\"\n\t\"github.com/vmware/purser/pkg/controller/eventprocessor\"\n\t\"github.com/vmware/purser/pkg/utils\"\n)\n\nvar conf controller.Config\n\n// InClusterConfigPath should be empty to get client and config for InCluster environment.\nconst InClusterConfigPath = \"\"\n\nvar interactions *string\n\nfunc init() {\n\tlogLevel := flag.String(\"log\", \"info\", \"set log level as info or debug\")\n\tdgraphURL := flag.String(\"dgraphURL\", \"purser-db\", \"dgraph zero url\")\n\tdgraphPort := flag.String(\"dgraphPort\", \"9080\", \"dgraph zero port\")\n\tinteractions = flag.String(\"interactions\", \"disable\", \"enable discovery of interactions\")\n\tkubeconfig := flag.String(\"kubeconfig\", InClusterConfigPath, \"path to the kubeconfig file\")\n\tflag.Parse()\n\n\tutils.InitializeLogger(*logLevel)\n\tconfig.Setup(&conf, *kubeconfig)\n\n\t// start dgraph and create login if not exists\n\tdgraph.Start(*dgraphURL, *dgraphPort)\n\tdgraph.StoreLogin()\n}\n\nfunc main() {\n\tgo api.StartServer(conf)\n\tgo startCronJobForPopulatingRateCard()\n\ttime.Sleep(time.Minute * 3)\n\tgo eventprocessor.ProcessEvents(&conf)\n\n\tif *interactions == \"enable\" {\n\t\tgo startInteractionsDiscovery()\n\t}\n\tgo startCronJobForUpdatingCustomGroups()\n\tcontroller.Start(&conf)\n}\n\n// starts first discovery after 5 min of controller starting. Next runs will occur in every 59 min\nfunc startInteractionsDiscovery() {\n\ttime.Sleep(time.Minute * 5)\n\trunDiscovery()\n\n\tc := cron.New()\n\terr := c.AddFunc(\"@every 0h59m\", runDiscovery)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\terr = c.AddFunc(\"@daily\", dgraph.RemoveResourcesInactive)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\tc.Start()\n}\n\nfunc runDiscovery() {\n\tprocessor.ProcessPodInteractions(conf)\n\tprocessor.ProcessServiceInteractions(conf)\n}\n\nfunc startCronJobForUpdatingCustomGroups() {\n\tquery.ComputeClusterAllocationAndCapacity()\n\trunGroupUpdate()\n\n\tc := cron.New()\n\terr := c.AddFunc(\"@every 0h5m\", runGroupUpdate)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\terr = c.AddFunc(\"@every 0h5m\", query.ComputeClusterAllocationAndCapacity)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\tc.Start()\n}\n\nfunc runGroupUpdate() {\n\teventprocessor.UpdateGroups(conf.Groupcrdclient)\n}\n\nfunc startCronJobForPopulatingRateCard() {\n\tcloud := &pricing.Cloud{Kubeclient: conf.Kubeclient}\n\t// find cloud provider and region\n\tcloud.CloudProvider, cloud.Region = pricing.GetClusterProviderAndRegion()\n\tcloud.PopulateRateCard()\n\n\tc := cron.New()\n\n\terr := c.AddFunc(\"@every 168h\", cloud.PopulateRateCard)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\tc.Start()\n}\n"
  },
  {
    "path": "cmd/plugin/purser.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/vmware/purser/pkg/client\"\n\tgroups_client_v1 \"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n\t\"github.com/vmware/purser/pkg/plugin\"\n\t\"github.com/vmware/purser/pkg/utils\"\n)\n\nconst (\n\tpluginVersion = \"version v1.0.0\"\n)\n\nvar (\n\tgroupClient *groups_client_v1.GroupClient\n\n\t// Variables used for cmd interface\n\tkubeconfig string\n\tinfo       string\n\tversion    string\n\n\tdescription   = fmt.Sprintf(\"Purser gives cost insights of kubernetes deployments.\\n\\n\")\n\tusage         = fmt.Sprintf(\"Usage:\\n  kubectl plugin purser [options] <command> <args>\\n\\n\")\n\tsupportedCmds = fmt.Sprintf(\"The supported commands are:\\n  get  Get resource information.\\n  set  Set resource information.\\n\\n\")\n\n\toptionHelp       = fmt.Sprintf(\"\\n  --info            Show more details about the plugin.\")\n\toptionKubeConfig = fmt.Sprintf(\"\\n  --kubeconfig      Absolute path for the kube config file.\")\n\toptionVersion    = fmt.Sprintf(\"\\n  --version         Show plugin version.\")\n\toptions          = fmt.Sprintf(\"options:%s%s%s\\n\\n\", optionHelp, optionKubeConfig, optionVersion)\n\n\tkubecltOption = fmt.Sprintf(\"\\nUse \\\"kubectl options\\\" for a list of global command-line options (applies to all commands).\\n\\n\")\n)\n\nfunc init() {\n\tflag.StringVar(&kubeconfig, \"kubeconfig\", os.Getenv(\"KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG\"), \"path to Kubernetes config file\")\n\n\tflag.StringVar(&info, \"info\", os.Getenv(\"KUBECTL_PLUGINS_LOCAL_FLAG_INFO\"), \"Show help documentation\")\n\tflag.StringVar(&version, \"version\", os.Getenv(\"KUBECTL_PLUGINS_LOCAL_FLAG_VERSION\"), \"Show version number\")\n\n\tflag.Usage = func() {\n\t\t_, err := fmt.Fprint(flag.CommandLine.Output(), description)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\t_, err = fmt.Fprint(flag.CommandLine.Output(), usage)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\t_, err = fmt.Fprint(flag.CommandLine.Output(), supportedCmds)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\t_, err = fmt.Fprint(flag.CommandLine.Output(), options)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\t_, err = fmt.Fprint(flag.CommandLine.Output(), \"Example(s):\\n\\n\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tprintHelp()\n\t\t_, err = fmt.Fprint(flag.CommandLine.Output(), kubecltOption)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\tif version != \"\" {\n\t\tfmt.Println(pluginVersion)\n\t\tos.Exit(0)\n\t}\n\n\tif info != \"\" {\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\t}\n\n\tconfig, err := utils.GetKubeconfig(kubeconfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tplugin.ProvideClientSetInstance(utils.GetKubeclient(config))\n\n\tclient, clusterConfig := client.GetAPIExtensionClient(kubeconfig)\n\tgroupClient = groups_client_v1.NewGroupClient(client, clusterConfig)\n}\n\nfunc main() {\n\tinputs := os.Args[2:] // index 1 is empty\n\tif len(inputs) == 4 && inputs[0] == Get {\n\t\tcomputeMetricInsight(inputs)\n\t} else if len(inputs) == 2 {\n\t\tcomputeStats(inputs)\n\t} else {\n\t\tprintHelp()\n\t}\n}\n\nfunc computeMetricInsight(inputs []string) {\n\tswitch inputs[1] {\n\tcase Cost:\n\t\tcomputeCost(inputs)\n\tcase Resources:\n\t\tfetchResource(inputs)\n\t}\n}\n\nfunc computeCost(inputs []string) {\n\tswitch inputs[2] {\n\tcase Label:\n\t\tplugin.GetPodsCostForLabel(inputs[3])\n\tcase Pod:\n\t\tplugin.GetPodCost(inputs[3])\n\tcase Node:\n\t\tplugin.GetAllNodesCost()\n\tdefault:\n\t\tprintHelp()\n\t}\n}\n\nfunc fetchResource(inputs []string) {\n\tswitch inputs[2] {\n\tcase Namespace:\n\t\tgroup := plugin.GetGroupByName(groupClient, inputs[3])\n\t\tif group != nil {\n\t\t\tplugin.PrintGroup(group)\n\t\t} else {\n\t\t\tfmt.Printf(\"Group %s is not present\\n\", inputs[3])\n\t\t}\n\tcase Label:\n\t\tif !strings.Contains(inputs[3], \"=\") {\n\t\t\tprintHelp()\n\t\t}\n\t\tgroup := plugin.GetGroupByName(groupClient, createGroupNameFromLabel(inputs[3]))\n\t\tif group != nil {\n\t\t\tplugin.PrintGroup(group)\n\t\t} else {\n\t\t\tfmt.Printf(\"Group %s is not present\\n\", inputs[3])\n\t\t}\n\tcase Group:\n\t\tgroup := plugin.GetGroupByName(groupClient, inputs[3])\n\t\tif group != nil {\n\t\t\tplugin.PrintGroup(group)\n\t\t} else {\n\t\t\tfmt.Printf(\"No group with name: %s\\n\", inputs[3])\n\t\t}\n\tdefault:\n\t\tprintHelp()\n\t}\n}\n\nfunc createGroupNameFromLabel(input string) string {\n\tinp := strings.Split(input, \"=\")\n\tkey, val := inp[0], inp[1]\n\tgroupName := key + \".\" + val\n\tif strings.Contains(groupName, \"/\") {\n\t\tgroupName = strings.Replace(groupName, \"/\", \"-\", -1)\n\t}\n\treturn strings.ToLower(groupName)\n}\n\nfunc computeStats(inputs []string) {\n\tswitch inputs[0] {\n\tcase Get:\n\t\tgetStats(inputs)\n\tcase Set:\n\t\tinputUserCosts(inputs)\n\tdefault:\n\t\tprintHelp()\n\t}\n}\n\nfunc getStats(inputs []string) {\n\tswitch inputs[1] {\n\tcase \"summary\":\n\t\tplugin.GetClusterSummary()\n\tcase \"savings\":\n\t\tplugin.GetSavings()\n\tcase \"user-costs\":\n\t\tprice := plugin.GetUserCosts()\n\t\tfmt.Printf(\"cpu cost per CPU per hour:\\t %f$\\nmem cost per GB per hour:\\t %f$\\nstorage cost per GB per hour:\\t %f$\\n\",\n\t\t\tprice.CPU,\n\t\t\tprice.Memory,\n\t\t\tprice.Storage)\n\tdefault:\n\t\tprintHelp()\n\t}\n}\n\nfunc inputUserCosts(inputs []string) {\n\tif inputs[1] == \"user-costs\" {\n\t\tfmt.Printf(\"Enter CPU cost per cpu per hour:\\t \")\n\t\tvar cpuCostPerCPUPerHour string\n\t\t_, err := fmt.Scan(&cpuCostPerCPUPerHour)\n\t\tlogError(err)\n\n\t\tfmt.Printf(\"Enter Memory cost per GB per hour:\\t \")\n\t\tvar memCostPerGBPerHour string\n\t\t_, err = fmt.Scan(&memCostPerGBPerHour)\n\t\tlogError(err)\n\n\t\tfmt.Printf(\"Enter Storage cost per GB per hour:\\t \")\n\t\tvar storageCostPerGBPerHour string\n\t\t_, err = fmt.Scan(&storageCostPerGBPerHour)\n\t\tlogError(err)\n\n\t\tplugin.SaveUserCosts(cpuCostPerCPUPerHour, memCostPerGBPerHour, storageCostPerGBPerHour)\n\t} else {\n\t\tprintHelp()\n\t}\n}\n\nfunc printHelp() {\n\tpluginExt := \"kubectl --kubeconfig=<absolute path to config> plugin purser \"\n\n\tfmt.Println(\"Try one of the following commands...\")\n\tfmt.Println(pluginExt + \"get summary\")\n\tfmt.Println(pluginExt + \"get resources group <group-name>\")\n\tfmt.Println(pluginExt + \"get cost label <key=val>\")\n\tfmt.Println(pluginExt + \"get cost pod <pod name>\")\n\tfmt.Println(pluginExt + \"get cost node all\")\n\tfmt.Println(pluginExt + \"set user-costs\")\n\tfmt.Println(pluginExt + \"get user-costs\")\n\tfmt.Println(pluginExt + \"get savings\")\n}\n\nfunc logError(err error) {\n\tif err != nil {\n\t\tlog.Printf(\"failed to read user input %+v\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/plugin/types.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\n// These are possible actions for resources\nconst (\n\tGet = \"get\"\n\tSet = \"set\"\n)\n\n// These are kubernetes components\nconst (\n\tLabel     = \"label\"\n\tPod       = \"pod\"\n\tNode      = \"node\"\n\tNamespace = \"namespace\"\n\tGroup     = \"group\"\n)\n\n// These are utilisation metrics\nconst (\n\tCost      = \"cost\"\n\tResources = \"resources\"\n)\n"
  },
  {
    "path": "docs/architecture.md",
    "content": "# Architecture of Purser\n\nThe following diagram represents the architecture of Purser.\n\n![Architecture](/docs/img/architecture.png)\n\nThe following are the main componenets installed in Kubernetes for Purser.\n\n1. **Kubernetes API Server**\n\n    All the Purser `kubectl` commands hit the API server extension. These APIs understand the input command, compute and return the required output.\n\n2. **Custom Controller**\n\n    The custom controller watches for changes in state of pods, nodes, persistent volumes, etc. and update the inventory in CRDs.\n\n3. **Custom Resource Definitions(CRDs)**\n\n    Custom Resource Definitions are like any other resource(Pod, Node, etc.) and store the config data like `Group Definitions` and inventory.\n\n4. **Metric Store**\n\n    Metric store is used to store the utilization, allocation metrics of inventory and also calculated costs.\n\n5. **CRON Job**\n\n    CRON Job collects the stats of inventory and calculates the cost periodically and stores in Metric Store.\n\n## Work Flow\n\n1. Purser installation steps create Custom Controller, CRON Job and CRDs in Kubernetes.\n\n2. Once installed the custom controller collects all the inventory(pods, nodes, pv, etc.) and stores in CRDs, later it watches for any changes in inventory and stores the changes in CRDs.\n\n3. CRON Job kicks in periodically and collect the stats and stores the stats in metric store. CRON Job also calculates the Costs in the same cycle and stores them in the metric store.\n\n4. Any `kubectl` command invocations are received by Kubernetes API server extension.  APIs then process the required output based on the configurations(for groups), inventory, costs metrics and returns to the user.\n"
  },
  {
    "path": "docs/custom-group-installation-and-usage.md",
    "content": "# Custom Group Installation and Usage\n\nTo get resource and cost visibility for a particular set of pods Purser allows user to create custom logical group.\nUser can define the label filter logic(`AND of ORs`: Conjunctive normal form) while creating the logical group i.e, pods satisfying these conditions will belong to this custom group.\n\n## Installing logical group definition and an example logical group\n\nTo install the logical group definition into your cluster, \ndownload [purser-group-crd.yaml](../cluster/artifacts/purser-group-crd.yaml) yaml i.e,\n```yaml\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: groups.vmware.purser.com\nspec:\n  group: vmware.purser.com\n  names:\n    kind: Group\n    listKind: GroupList\n    plural: groups\n    singular: group\n  scope: Namespaced\n  version: v1\nstatus:\n  acceptedNames:\n    kind: Group\n    listKind: GroupList\n    plural: groups\n    singular: group\n```\nand use kubectl to install this definition\n```bash\nkubectl create -f purser-group-crd.yaml\n```\n_**NOTE:** This installation is needed only once per cluster_\n\n**Installing an example logical group**\n\nDownload [example-group.yaml](../cluster/artifacts/example-group.yaml) yaml i.e,\n```yaml\napiVersion: vmware.purser.com/v1\nkind: Group\nmetadata:\n  name: example-group\nspec:\n  name: example-group\n  labels:\n    expr1:\n      app:\n        - sample-app\n        - sample-app2\n      env:\n        - dev\n    expr2:\n      namespace:\n        - ns1\n        - ns2\n    expr3:\n      key1:\n        - val1\n      key2:\n        - val2\n```\nand use kubectl to create this logical group\n```bash\nkubectl create -f example-group.yaml\nkubectl get groups.vmware.purser.com\n```\n\nThis will create a custom logical group with name `example-group` of type `groups.vmware.purser.com`.\nThe label filter (used to fetch pods belonging to this group) for `example-group` will be\n```yaml\n(app=sampl-app OR app=sample-app2 OR env=dev) AND (namespace=ns1 OR namespace=ns2) AND (key1=val1 OR key2=val2)\n```\n\nIn general the syntax purser supports is:\n\n```\nexpr1 AND expr2 AND expr3 AND ...\nwhere each expr is of form key1:value1 OR key2:value2 OR key1:value3 OR ...\n```\n\n## Usage\nFor resource and cost visibility into this newly created logical group run the following command\n```bash\nkubectl plugin purser get resources group example-group\n```\n_Refer [purser installation](../README.md#installation) to install purser controller and plugin_ \n\n## Uninstalling purser custom group\nTo uninstall purser custom group run the following command\n```bash\nkubectl delete -f purser-group-crd.yaml\n```\nwhere [purser-group-crd.yaml](../cluster/artifacts/purser-group-crd.yaml) is same file that you downloaded during installation."
  },
  {
    "path": "docs/design/pricing.md",
    "content": "# Pricing in Purser\n\n## User defined pricing\nCurrently purser supports user defined pricing for cpu, memory and storage resources per hour. The default pricing for the resources are\n\n* CPU: 0.024$  per vCPU per Hour\n* Memory:  0.01$ per GB per Hour\n* Storage:  0.00013888888$ per GB per Hour\n\nThese default pricing for cpu and memory have been set by taking average [prices](https://aws.amazon.com/ec2/pricing/on-demand/) in AWS ec2 instances.\nFor storage we set pricing proportional to 0.1$ per GB per month referring to AWS [ebs pricing](https://aws.amazon.com/ebs/pricing/).\n\nUser can edit these pricing using purser plugin: `kubectl plugin purser set user-costs`\n\n_(Future work)_ Option to edit these default pricing in UI.\n\n## Using node labels for accurate pricing (WIP)\nData needed to get correct pricing of node:\n\n* Cloud Provider (AWS, GCE etc)\n* Region (us-east etc)\n* Machine Type (t2.micro, m4.large)\n* Operating system (linux, windows etc)\n* Rate card which gives cost of node depending on above data\n* _(Future work)_ Costing based on instance type i.e., \"Is the instance On-demand or Spot-Instance or Reserved-Instance etc?\"\n* _(Future work)_ Discounts\n\n### Getting cloud provider, region and machine type\nKubelet populates few [reserved labels](https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#beta-kubernetes-io-instance-type) on nodes. Using these labels we can determine region, machineType and operating system.\nCommand `kubectl describe node <nodeName>` gives labels.\n\n* Default assume cloud provider as aws. Check section [Finding Cloud Provider](#finding-cloud-provider).\n* Label `beta.kubernetes.io/instance-type=m4.10xlarg` gives machine type. Here for this example machineType is m4.10xlarge\n* Label `beta.kubernetes.io/os=linux` gives operating system. Here os is linux\n* Label `failure-domain.beta.kubernetes.io/region=us-west-1` gives region. Here region is us-west-1\n\n_Note: kubelet will not set these reserved labels if the cluster is not using cloud provider._\n\nIf any of the required labels is not available then we should fall back to default pricing.\n\n\n\n#### Storage Volume Pricing\n\n`kubectl get pv` gives us storage class(ex: gp2, my-storage-class etc) for each volume.\n\nFurther using command `kubectl describe storageclass <storageclass-name>` will give output in which there will be a field `Parameters`\n containing labels for `type` of storage (Ex: gp2) and `zone` (Ex: us-west-1c).\n\n_Parameters_ field may not contain _zone_ label. In such case we can get region from `kubectl describe pv <pv-name>` using label `failure-domain.beta.kubernetes.io/region`.\n\nIf _type_ label is also not present then we should fall back to default pricing.\n\n\n\n### Finding Cloud Provider\nWhile initiating a cluster either by kubeadm or kops or other kubernetes installers the user will set cloud-provider, if it isn't set kubernetes assumes that cluster is being deployed on bare metal. \nFurther when a new node is created `.spec.providerID` will be set based (by _kubelet_) on cloud-provider.\nThe value of `providerId` will be `(providerName +  \"://\" + instanceID)`. As we need `providerName` (aws, azure etc) we can get it from `providerID`.\nIf getting cloud provider name fails we should fallback and assume default prices for aws.\n\n*Example cluster on aws: --cloud-provider=aws command-line flag is needed (to successfully register the node with cloud provider) to be present for the API server, controller manager, and every kubelet in the cluster.\n\nReferences:\n\n* kubeadm: https://kubernetes.io/docs/concepts/cluster-administration/cloud-providers/\n* providerID value: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/cloud-provider/cloud.go#L94\n* Cluster on aws: https://blog.heptio.com/setting-up-the-kubernetes-aws-cloud-provider-6f0349b512bd\n* More on providerID: https://blog.scottlowe.org/2018/09/28/setting-up-the-kubernetes-aws-cloud-provider/\n\n\n*Note: All above kubectl commands will have corresponding methods in kubernetes client-go\n\n\n\n### Populating Rate Card\n#### Design\n\n* Identify cloud provider, region of the cluster\n* Embed the crawler code in purser and run cloud specific crawler to fetch the rate cards.\n* Populate the data in dgraph.\n* Update rate card periodically.\n* Support: AWS, Azure, PKS, VKE, GCE\n\n#### AWS:\n\n* Public API: Available\n* Reference: https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/price-changes.html\n* API call: https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/region/index.json\n* Example for us-east-1: https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/us-east-1/index.json\n* Note: aws provides sdk in golang for pricing. Reference: https://docs.aws.amazon.com/sdk-for-go/api/service/pricing/"
  },
  {
    "path": "docs/developers-guide.md",
    "content": "# Developers Guide\n\n- [Prerequisites](#prerequisites)\n- [Workspace Setup](#workspace-setup)\n- [Database Setup](#database-setup)\n- [Running Purser Controller](#running-purser-controller)\n- [Running Purser UI](#running-purser-ui)\n- [Purser Plugin Compilation](#purser-plugin-compilation)\n- [Plugin Execution](#plugin-execution)\n\n## Prerequisites\n\n1. Ensure the following dependencies are installed on your system.\n\n   - [Go](https://golang.org/dl/)\n   - [Git](https://git-scm.com/downloads)\n   - [Docker](https://www.docker.com/)\n\n   You may use the official binaries or your usual package manager.\n   Also set the following environment variables\n   - Set `GOPATH` environment variable. Refer [setting GOPATH](https://github.com/golang/go/wiki/SettingGOPATH)\n   - Add `$GOPATH/bin` in system `PATH` variable by running `export PATH=$PATH:$GOPATH/bin`.\n\n      Optionally, add the above exports to your `.bash_profile` or `.bashrc` to persist across console sessions.\n\n2. Verify that the dependencies are properly installed.\n\n   ``` bash\n   go version, should be at least 1.7\n\n   git version\n\n   docker version\n   ```\n\n## Workspace Setup\n\n### Fork the repository\nNavigate to the [Purser repo on GitHub](https://github.com/vmware/purser) and use the 'Fork' button. \nThis gives you a copy of the repo for pull requests back to purser in `https://github.com/<your-github-id>/purser`\n\n### Clone and Set Upstream Remote\n\nMake a local clone of the forked repo and add the base purser\nrepo as the upstream remote repository.\n\n``` shell\n# create and change directory to $GOPATH/src/github.com/vmware\nmkdir -p $GOPATH/src/github.com/vmware\ncd $GOPATH/src/github.com/vmware\n\n# clone the forked repository and change directory to purser\ngit clone https://github.com/<your-github-id>/purser.git\ncd purser\n\n# add upstream repository as the original purser repo\ngit remote add upstream https://github.com/vmware/purser.git\n```\n\nThe last git command prepares your clone to pull changes from the\nupstream repo and push them into the fork, which enables you to keep\nthe fork up to date.\n\n### Download dependencies\n\nRun the following commands to download dependencies.\n\n``` shell\nmake tools\nmake deps\nmake install\n```\n\n## Database Setup\n\nIn order to persist inventory and discovery information such as pods and service details we use\n[Dgraph](https://dgraph.io/) to store the inventory metrics and resource relationship.\n\nIn order to install DGraph from docker image follow the following steps:\n\n- Pull the latest Dgraph version\n\n  ```bash\n  docker pull dgraph/dgraph\n  ```\n\n- To run Dgraph in Docker\n\n  ```bash\n  mkdir -p /tmp/data\n  \n  # Run dgraph-zero\n  docker run -d -p 5080:5080 -p 6080:6080 -p 8080:8080 -p 9080:9080 -p 8000:8000 -v /tmp/data:/dgraph --name diggy dgraph/dgraph dgraph zero\n\n  # In another terminal, now run dgraph-alpha\n  docker exec -d diggy dgraph alpha --lru_mb 2048 --zero localhost:5080\n  ```\n \n - Optional: To start Dgraph UI(at `localhost:8000`) for running manual queries\n   ```bash\n   # Run Dgraph Ratel\n   docker exec -d diggy dgraph-ratel\n   ```\n   \n## Running Purser Controller\nTo run purser controller execute following commands\n\n```bash\n# change directory to purser main folder\ncd $GOPATH/src/github.com/vmware/purser\n\n# run purser with log level as info and interactions as disabled by default\ngo run cmd/controller/purserctrl.go --kubeconfig=<path-to-your-cluster-config> --interactions=disable --dgraphURL=localhost --log=info\n```\n\n## Running Purser UI\nInstall latest version of `node` and `npm`. Then to run purser UI execute the following commands\n```bash\n# change directory to purser ui folder\ncd $GOPATH/src/github.com/vmware/purser/ui\n\n# install node modules\nnpm install\n\n# run purser UI at localhost:4200\nnpm run startdev\n```\n_Refer [UI docs](../ui/README.md) for more details._\n\n## Purser Plugin Compilation\n\nTo create purser plugin binary `purser_plugin` at path `$GOPATH/bin` run the following commands\n  ```bash\n  # change directory to purser main folder\n  cd $GOPATH/src/github.com/vmware/purser\n  \n  # create binary at path $GOPATH/bin\n  go build -o $GOPATH/bin/purser_plugin github.com/vmware/purser/cmd/plugin\n  ```\n\n**NOTE:** _Windows users need to rename `purser_plugin` to `purser_plugin.exe`_\n\n## Plugin Execution\n\n1. In order to install the Purser plugin, copy the [plugin.yaml](../plugin.yaml) file to one of the specified paths defined under the section [installing kubectl plugins](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/).\n\n2. Run the following command to check the purser plugin works locally.\n\n   ``` bash\n   kubectl --kubeconfig=<absolute path to kubeconfig file> plugin purser help\n   ```\n\n## Useful commands and links\n- To contribute to purser refer [CONTRIBUTING](../CONTRIBUTING.md) and [CODE_OF_CONDUCT](../CODE_OF_CONDUCT.md)\n- To drop complete dgraph database: `curl -X POST localhost:8080/alter -d '{\"drop_all\": true}'`"
  },
  {
    "path": "docs/manual-installation.md",
    "content": "# Manual Installation\n\nTo install Purser manually from the Binary follow the steps described below.\n\n## Purser Setup\nThe following steps will install Purser in your cluster at namespace `purser`.\nCreation of this namespace is needed because purser needs to create a service-account which requires namespace.\nAlso, the frontend will use kubernetes DNS to call backend for data and this DNS contains a field for namespace.\n``` bash\n# Namespace setup\nkubectl create ns purser\n\n# DB setup\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-database-setup.yaml -O\nkubectl --namespace=purser create -f purser-database-setup.yaml\n\n# Purser controller setup\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-controller-setup.yaml -O\nkubectl --namespace=purser create -f purser-controller-setup.yaml\n\n# Purser UI setup\ncurl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-ui-setup.yaml -O\nkubectl --namespace=purser create -f purser-ui-setup.yaml\n```\n**NOTE:** If you don't have `curl` installed you can download `purser-database-setup.yaml` from [here](./cluster/purser-database-setup.yaml), `purser-controller-setup.yaml` from [here](cluster/purser-controller-setup.yaml) and `purser-ui-setup.yaml` from [here](cluster/purser-ui-setup.yaml). \nThen `kubectl create -f purser-database-setup.yaml` ,\n`kubectl create -f purser-controller-setup.yaml` and `kubectl create -f purser-ui-setup.yaml` will setup purser in your cluster.\n\n##### Change Settings and Enable/Disable Purser Features\n\nThe following settings can be customized before Controller installation:\n\n- Change the default **log level**, **dgraph url** and **dgraph port** by editing `args` field in the [purser-controller-setup.yaml](cluster/purser-controller-setup.yaml). (Default: `--log=info`, `--dgraphURL=purser-db`, `--dgraphPort=9080`)\n- Enable/Disable **resource interactions** capability by editing `args` field in the [purser-controller-setup.yaml](cluster/purser-controller-setup.yaml) and uncommenting `pods/exec` rule from purser-permissions. (Default: `disabled`)\n- Enable **subscription to inventory changes** capability by creating an object of custom resource kind `Subscriber`. (Refer: [example-subscriber.yaml](./cluster/artifacts/example-subscriber.yaml))\n- Enable **customized logical grouping of resources** by creating an object of custom resource kind `Group`. (Refer: [docs](docs/custom-group-installation-and-usage.md) for custom group installation and usage)\n\n_**NOTE:** Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._\n\n## Purser Plugin Installation (Optional)\n\n- Download the purser plugin descriptor for your environment from the [releases page](https://github.com/vmware/purser/releases/download/v1.0.0/plugin.yaml).\n\n- Move the `plugin.yaml` file into one of the paths specified under the Kubernetes [documentation](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins).\n\n- Download the purser binary corresponding to your operating system from the [releases page](https://github.com/vmware/purser/releases/tag/v1.0.0).\n\n- Move the binary into one of the directories in your environment `PATH`.\n"
  },
  {
    "path": "docs/plugin-installation.md",
    "content": "# Purser Plugin Setup\n_NOTE: This Plugin installation is optional. Install it if you want to use CLI of Purser._\n\n## Linux and macOS\n\n``` bash\n# Binary installation\nwget -q https://github.com/vmware/purser/blob/master/build/purser-binary-install.sh && sh purser-binary-install.sh\n```\n\nEnter your cluster's configuration path when prompted. The plugin binary needs to be in your `PATH` environment variable, so once the download of the binary is finished the script tries to move it to `/usr/local/bin`. This may need your sudo permission.\n\n## Windows/Others\n\nFor installation on Windows follow the steps in the [manual installation guide](./docs/manual-installation.md).\n\n## Uninstalling Purser Plugin\n\n### Linux/macOS\n\n``` bash\ncurl https://raw.githubusercontent.com/vmware/purser/master/build/purser-binary-install.sh -O && sh purser-binary-uninstall.sh\n```\n"
  },
  {
    "path": "docs/plugin-usage.md",
    "content": "# Purser Plugin Usage\n\nOnce installed, Purser is ready for use right away. You can query using native Kubernetes grouping artifacts.\n\nPurser supports the following list of commands.\n\n``` bash\n# query cluster visibility in terms of savings and summary for the application.\nkubectl plugin purser get [summary|savings]\n\n# query resources filtered by associated namespace, labels and groups.\nkubectl plugin purser get resources group <group-name>\n\n# query cost filtered by associated labels, pods and node.\nkubectl plugin purser get cost label <key=val>\nkubectl plugin purser get cost pod <pod name>\nkubectl plugin purser get cost node all\n\n# configure user-costs for the choice of deployment.\nkubectl plugin purser [set|get] user-costs\n```\n\n_Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._\n\n## Examples\n\n1. Get Cluster Summary\n\n   ``` bash\n   $ kubectl plugin purser get summary\n        Cluster Summary\n        Compute:\n            Node count:                 57\n            Cost:                       3015.48$\n            Total Capacity:\n                Cpu(vCPU):               456\n                Memory(GB):              1770.50\n            Provisioned Resources:\n                Cpu Request(vCPU):       319\n                Memory Request(GB):      1032.67\n        Storage:\n            Persistent Volume count:    151\n            Capacity(GB):               9297.00\n            Cost:                       4124.79$\n            PV Claim count:             108\n            PV Claim Capacity(GB):      8867.00\n        Cost:\n            Compute cost:               3015.48$\n            Storage cost:               4124.79$\n            Total cost:                 7140.27$\n    ```\n\n\n2. Get Cost Of All Nodes\n\n    ``` bash\n    kubectl purser get cost node all\n    ```\n\n3. Get Savings\n\n    ``` bash\n    $ kubectl plugin purser get savings\n        Savings Summary\n        Storage:\n            Unused Volumes:             43\n            Unused Capacity(GB):        430.00\n            Month To Date Savings:      186.33$\n            Projected Monthly Savings:   1066.40$\n    ```\n\nNext, define higher level groupings to define your business, logical or application constructs.\n\n## Defining Custom Groups\n\nRefer [doc](./custom-group-installation-and-usage.md) for custom group installation and usage. "
  },
  {
    "path": "docs/purser-deployment.md",
    "content": "# Purser Deployment\n\nIn order to deploy the Purser UI and DGraph database service, follow the below listed steps:\n\n1. Switch the current context to point to the desired cluster.\n\n    ``` bash\n    kubectl config use-context <context>\n    ```\n\n    Read more about configuring and setting the `KUBECONFIG` and kubernetes context [here](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/).\n\n2. If the cluster does not have a valid public IP, set proxy in order to expose the service externally.\n\n    ``` bash\n    kubectl proxy\n    ```\n\n3. When set, you can simply deploy the Purser UI and Dgraph database service using target `make deploy-purser`.\n\n   _If you wish to however, deploy the database service and the UI service separately, execute the following targets respectively._\n\n   ``` bash\n   # deploy Dgraph database\n   make kubectl-deploy-purser-db\n\n   # deploy purser UI\n   make kubectl-deploy-purser-ui\n   ```\n\n4. Once deployed, if proxy was set the UI service can be accessed from [this url](http://127.0.0.1:8001/api/v1/namespaces/default/services/http:purser-ui:4200/proxy/home).\n\n    If public IP was available for your cluster, the UI service should be accessible from path `<External-Public-IP>:<NodePort>`.\n\n    Eg. `http://<minishiftIP>:<NodePort>/home`\n\n5. In order to drop the Dgraph entries from the database, delete the `Persistent Volume` corresponding to the `dgraph datadir`."
  },
  {
    "path": "docs/sourcecode-installation.md",
    "content": "# Installation Through Source Code\n\n- [Prerequisites](#prerequisites)\n- [Server Side Installation (Controller Installation)](#server-side-installation-controller-installation)\n- [Client Side Installation (Plugin Installation)](#client-side-installation-plugin-installation)\n\n## Prerequisites\n\n1. Kubernetes Version 1.9 or greater\n\n    - `kubectl` installed and configured. For details refer [here](https://kubernetes.io/docs/tasks/tools/install-kubectl/).\n\n2. Dependencies\n\n    - [Go](https://golang.org/dl/)\n\n        - version > 1.7\n        - setup `GOPATH` environment variable by as per the [Golang documentation](https://github.com/golang/go/wiki/SettingGOPATH).\n        - add `$GOPATH/bin` directory to your environment `$PATH` variable.\n\n    - [Docker](https://www.docker.com/get-started)\n\n3. Fetch the Purser source code from GitHub.\n\n   ``` go\n   go get github.com/vmware/purser\n   ```\n\n   ``` bash\n   # change directory to project root\n   cd $GOPATH/src/github.com/vmware/purser\n   ```\n\n4. For Windows users, install gnu `make` from [here](http://gnuwin32.sourceforge.net/packages/make.htm).\n\n5. Download project dependencies with `make`.\n\n   ``` bash\n   # download project tools\n   make tools\n\n   # download project dependencies\n   make deps\n\n   # update project depedencies\n   make update\n   ```\n\n## Server Side Installation (Controller Installation)\n\nFollow the below steps to install the purser controller and custom resource definitions for the user groups in the Kubernetes cluster.\n\n### Build Controller Binary\n\nBuild the purser controller binary using `make` target.\n\n``` bash\nmake build\n```\n\n### Build Container Image\n\nUpdate the [Makefile](./Makefile) to set the `REGISTRY` field to your Docker username and execute the following `make` targets to build and publish the docker images.\n\n``` bash\n# create the container(docker image)\nmake container\n\n# authenticate your Docker credentials\ndocker login\n\n# publish your docker image to docker hub\nmake push\n```\n\n### Install Purser Plugin\n\n- Update the image name in [`purser-controller-setup.yaml`](../cluster/purser-controller-setup.yaml) to the docker image name that you pushed.\n\n- Install the controller in the cluster using `kubectl`.\n\nThe following steps will install Purser in your cluster at namespace `purser`.\nCreation of this namespace is needed because purser needs to create a service-account which requires namespace.\nAlso, the frontend will use kubernetes DNS to call backend for data and this DNS contains a field for namespace.\n\n  ``` bash\n  # Namespace setup\n  kubectl create ns purser\n  \n  # DB setup\n  curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-database-setup.yaml -O\n  kubectl --namespace=purser create -f purser-database-setup.yaml\n  \n  # Purser controller setup\n  kubectl --namespace=purser create -f purser-controller-setup.yaml\n  \n  # Purser UI setup\n  curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-ui-setup.yaml -O\n  kubectl --namespace=purser create -f purser-ui-setup.yaml\n  ```\n\n  _Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._\n\n## Client Side Installation (Plugin Installation)\n\n- Build the purser plugin binary in the `GOPATH/bin` directory.\n\n  ``` go\n  go build -o $GOPATH/bin/purser_plugin github.com/vmware/purser/cmd/plugin\n  ```\n\n- Install the Purser plugin by copying the [`plugin.yaml`](../plugin.yaml) into one of the paths specified under the Kubernetes documentation section [installing kubectl plugins](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/)."
  },
  {
    "path": "openapi.yaml",
    "content": "---\nopenapi: 3.0.1\ninfo:\n  title: Purser\n  description: Purser runs on server port `:3030` and exposes API endpoints to generate an insight into your Kubernetes applications by providing details of communicating services and pods.\n  version: 1.0.0\nservers:\n  - url: http://localhost:3030\npaths:\n  /api/hierarchy:\n    get:\n      description: Gets the top level cluster hierachy\n      parameters:\n        - name: view\n          in: query\n          description: physical or logical depending on selection of physical entities such as nodes, persistent volumes or logical entities such as namespaces, pods etc. Default is logical.\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: physical\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/namespace:\n    get:\n      description: Gets the K8s Namespace hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Namespace name prefixed with `namespace-`\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: namespace-kube-public\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/pvc:\n    get:\n      description: Gets the K8s PVC hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s PVC name prefixed with `pvc-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pvc-datadir-dgraph-0\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/job:\n    get:\n      description: Gets the K8s Job hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Job name prefixed with `job-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: job-kube-proxy\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/container:\n    get:\n      description: Gets the K8s container hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s container name prefixed with `container-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: container-etcd\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/replicaset:\n    get:\n      description: Gets the K8s Replicaset hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Replicaset name prefixed with `replicaset-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: replicaset-kube-dns-86f4d74b45\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/pod:\n    get:\n      description: Gets the K8s Pod hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Pod name prefixed with `pod-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pod-etcd-minikube\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/node:\n    get:\n      description: Gets the K8s Node hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Node name prefixed with `node-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: node-minikube\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/daemonset:\n    get:\n      description: Gets the K8s Daemonset hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Daemonset name prefixed with `daemonset-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: daemonset-kube-proxy\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/deployment:\n    get:\n      description: Gets the K8s Deployment hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Deployment name prefixed with `deployment-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: deployment-kube-dns\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/pv:\n    get:\n      description: Gets the K8s PV hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s PV name prefixed with `pv-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pv-pvc-5ffeaa3f-ed5e-11e8-b395-080027a0bfc5\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/statefulset:\n    get:\n      description: Gets the K8s Statefulset hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Statefulset name prefixed with `statefulset-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: statefulset-kube-dns-86f4d74b45\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/hierarchy/process:\n    get:\n      description: Gets the K8s container process hierachy\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s container process name prefixed with `process-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: process-etcd\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Hierarchy'\n  /api/metrics:\n    get:\n      description: Gets the complete K8s cluster metrics\n      parameters:\n        - name: view\n          in: query\n          description: physical or logical depending on selection of physical entities such as nodes, persistent volumes or logical entities such as namespaces, pods etc. Default is logical.\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: logical\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/namespace:\n    get:\n      description: Gets the K8s Namespace metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Namespace name prefixed with `namespace-`\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: namespace-kube-public\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/pvc:\n    get:\n      description: Gets the K8s PVC metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s PVC name prefixed with `pvc-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pvc-datadir-dgraph-0\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/job:\n    get:\n      description: Gets the K8s Job metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Job name prefixed with `job-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: job-kube-proxy\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/container:\n    get:\n      description: Gets the K8s container metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s container name prefixed with `container-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: container-etcd\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/replicaset:\n    get:\n      description: Gets the K8s Replicaset metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Replicaset name prefixed with `replicaset-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: replicaset-kube-dns-86f4d74b45\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/pod:\n    get:\n      description: Gets the K8s Pod metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Pod name prefixed with `pod-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pod-etcd-minikube\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/node:\n    get:\n      description: Gets the K8s Node metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Node name prefixed with `node-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: node-minikube\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/daemonset:\n    get:\n      description: Gets the K8s Daemonset metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Daemonset name prefixed with `daemonset-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: daemonset-kube-proxy\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/deployment:\n    get:\n      description: Gets the K8s Deployment metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Deployment name prefixed with `deployment-`\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: deployment-kube-dns\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/pv:\n    get:\n      description: Gets the K8s PV metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s PV name prefixed with `pv-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pv-pvc-5ffeaa3f-ed5e-11e8-b395-080027a0bfc5\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/metrics/statefulset:\n    get:\n      description: Gets the K8s Statefulset metrics\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Statefulset name prefixed with `statefulset-`\n          required: true\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: statefulset-kube-dns-86f4d74b45\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Metrics'\n  /api/interactions/pod:\n    get:\n      description: Gets K8s Pods interactions\n      parameters:\n        - name: name\n          in: query\n          description: a valid K8s Pod name prefixed with `pod-`\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: string\n          example: pod-kube-dns-86f4d74b45-4v66p\n        - name: orphan\n          in: query\n          description: filters out orphan pods if set to false. Default is true\n          required: false\n          style: FORM\n          explode: true\n          schema:\n            type: boolean\n          example: \"false\"\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                $ref: '#/components/schemas/Interactions'\n  /api/edges:\n    get:\n      description: Gets edges between Dgraph Components\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Edges'\n  /api/nodes:\n    get:\n      description: Gets Dgraph node Components\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Nodes'\n  /api/groups:\n    get:\n      description: Gets array of Group objects along with their metrics\n      responses:\n        200:\n          description: Operation Successful\n          content:\n            application/json; charset=UTF-8:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Groups'\ncomponents:\n  schemas:\n    Hierarchy:\n      type: object\n      properties:\n        data:\n          $ref: '#/components/schemas/Hierarchy_data'\n    Metrics:\n      type: object\n      properties:\n        data:\n          $ref: '#/components/schemas/Metrics_data'\n    Interactions:\n      type: object\n      properties:\n        pods:\n          type: array\n          items:\n            $ref: '#/components/schemas/Interactions_pods'\n    Nodes:\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int32\n          example: 1\n        label:\n          type: string\n          example: postgres-54f9679f4b-nj8vv\n        title:\n          type: string\n          example: pods\n        value:\n          type: integer\n          format: int32\n          example: 1\n        group:\n          type: integer\n          format: int32\n          example: 1\n        cid:\n          type: array\n          items:\n            type: string\n            example: postgres\n    Edges:\n      type: object\n      properties:\n        from:\n          type: integer\n          format: int32\n          example: 4\n        to:\n          type: integer\n          format: int32\n          example: 3\n        title:\n          type: string\n          example: 7 times communicated\n    Groups:\n      type: object\n      properties:\n        name:\n          type: string\n          example: Group-1\n        podsCount:\n          type: integer\n          format: int32\n          example: 4\n        mtdCPU:\n          type: number\n          example: 10.2\n        mtdMemory:\n          type: number\n          example: 3.9\n        mtdStorage:\n          type: number\n          example: 41.5\n        cpu:\n          type: number\n          example: 2.04\n        memory:\n          type: number\n          example: 0.78\n        storage:\n          type: number\n          example: 8.3\n        mtdCPUCost:\n          type: number\n          example:  0.2448\n        mtdMemoryCost:\n          type: number\n          example: 0.039\n        mtdStorageCost:\n          type: number\n          example: 0.00576388852\n        mtdCost:\n          type: number\n          example: 0.28956388852\n    Hierarchy_data_children:\n      type: object\n      properties:\n        name:\n          type: string\n          example: namespace-default\n        type:\n          type: string\n          example: namespace\n    Hierarchy_data:\n      type: object\n      properties:\n        name:\n          type: string\n          example: cluster\n        type:\n          type: string\n          example: cluster\n        children:\n          type: array\n          items:\n            $ref: '#/components/schemas/Hierarchy_data_children'\n    Metrics_data_children:\n      type: object\n      properties:\n        name:\n          type: string\n          example: namespace-default\n        type:\n          type: string\n          example: namespace\n        cpu:\n          type: number\n          example: 0.915\n        memory:\n          type: number\n          example: 0.224609\n        cpuCost:\n          type: number\n          example: 0.02196\n        memoryCost:\n          type: number\n          example: 0.002246\n    Metrics_data:\n      type: object\n      properties:\n        name:\n          type: string\n          example: cluster\n        type:\n          type: string\n          example: cluster\n        children:\n          type: array\n          items:\n            $ref: '#/components/schemas/Metrics_data_children'\n        cpu:\n          type: number\n          example: 0.915\n        memory:\n          type: number\n          example: 0.224609\n        cpuCost:\n          type: number\n          example: 0.02196\n        memoryCost:\n          type: number\n          example: 0.002246\n    Interactions_inbound:\n      type: object\n      properties:\n        name:\n          type: string\n          example: pod-webapp-958cf5567-xb758\n    Interactions_pods:\n      type: object\n      properties:\n        name:\n          type: string\n          example: pod-postgres-54f9679f4b-vsdhl\n        inbound:\n          type: array\n          items:\n            $ref: '#/components/schemas/Interactions_inbound'\n        outbound:\n          type: array\n          items:\n            $ref: '#/components/schemas/Interactions_inbound'\n  extensions: {}\n"
  },
  {
    "path": "pkg/apis/groups/v1/deepcopy.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport \"k8s.io/apimachinery/pkg/runtime\"\n\n// DeepCopyInto copies all properties of this object into another object of the\n// same type that is provided as a pointer.\nfunc (in *Group) DeepCopyInto(out *Group) {\n\tout.TypeMeta = in.TypeMeta\n\tout.ObjectMeta = in.ObjectMeta\n\tout.Spec = in.Spec\n\tout.Status = in.Status\n}\n\n// DeepCopyObject returns a generically typed copy of an object\nfunc (in *Group) DeepCopyObject() runtime.Object {\n\tout := Group{}\n\tin.DeepCopyInto(&out)\n\treturn &out\n}\n\n// DeepCopyObject returns a generically typed copy of an object\nfunc (in *GroupList) DeepCopyObject() runtime.Object {\n\tout := GroupList{}\n\tout.TypeMeta = in.TypeMeta\n\tout.ListMeta = in.ListMeta\n\n\tif in.Items != nil {\n\t\tout.Items = make([]*Group, len(in.Items))\n\t\tfor i := range in.Items {\n\t\t\tin.Items[i].DeepCopyInto(out.Items[i])\n\t\t}\n\t}\n\treturn &out\n}\n"
  },
  {
    "path": "pkg/apis/groups/v1/docs.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n"
  },
  {
    "path": "pkg/apis/groups/v1/register.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\n// SchemeBuilder parameters\nvar (\n\tSchemeBuilder = runtime.NewSchemeBuilder(AddKnownTypes)\n\tAddToScheme   = SchemeBuilder.AddToScheme\n)\n\n// SchemeGroupVersion is group version used to register these objects\nvar SchemeGroupVersion = schema.GroupVersion{Group: CRDGroup, Version: CRDVersion}\n\n// Kind takes an unqualified kind and returns a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// AddKnownTypes ...\nfunc AddKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Group{},\n\t\t&GroupList{},\n\t)\n\tmeta_v1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apis/groups/v1/types.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/metrics\"\n\t\"time\"\n\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// CRD Group attributes\nconst (\n\tCRDPlural   string = \"groups\"\n\tCRDGroup    string = \"vmware.purser.com\"\n\tCRDVersion  string = \"v1\"\n\tFullCRDName string = CRDPlural + \".\" + CRDGroup\n)\n\n// Group describes our custom Group resource\ntype Group struct {\n\tmeta_v1.TypeMeta   `json:\",inline\"`\n\tmeta_v1.ObjectMeta `json:\"metadata\"`\n\tSpec               GroupSpec   `json:\"spec\"`\n\tStatus             GroupStatus `json:\"status,omitempty\"`\n}\n\n// GroupSpec is the spec for the Group resource\ntype GroupSpec struct {\n\tName               string                         `json:\"name\"`\n\tType               string                         `json:\"type,omitempty\"`\n\tExpressions        map[string]map[string][]string `json:\"labels,omitempty\"`\n\tAllocatedResources *GroupMetrics                  `json:\"metrics,omitempty\"`\n\tPITMetrics         *GroupMetrics                  `json:\"pitMetrics,omitempty\"`\n\tMTDMetrics         *GroupMetrics                  `json:\"mtdMetrics,omitempty\"`\n\tMTDCost            *Cost                          `json:\"mtdCost,omitempty\"`\n\tPerHourCost        *Cost                          `json:\"perHourCost,omitempty\"`\n\tLastMonthCost      *Cost                          `json:\"lastMonthCost,omitempty\"`\n\tLastLastMonthCost  *Cost                          `json:\"lastLastMonthCost,omitempty\"`\n\tLastUpdated        time.Time                      `json:\"lastUpdated,omitempty\"`\n}\n\n// GroupMetrics ...\ntype GroupMetrics struct {\n\tCPULimit        float64\n\tMemoryLimit     float64\n\tStorageCapacity float64\n\tCPURequest      float64\n\tMemoryRequest   float64\n\tStorageClaim    float64\n}\n\n// Cost details\ntype Cost struct {\n\tTotalCost   float64\n\tCPUCost     float64\n\tMemoryCost  float64\n\tStorageCost float64\n}\n\n// GroupList is the list of Group resources\ntype GroupList struct {\n\tmeta_v1.TypeMeta `json:\",inline\"`\n\tmeta_v1.ListMeta `json:\"metadata\"`\n\tItems            []*Group `json:\"items\"`\n}\n\n// GroupStatus holds the status information for each Group resource\ntype GroupStatus struct {\n\tState   string `json:\"state,omitempty\"`\n\tMessage string `json:\"message,omitempty\"`\n}\n\n// PodDetails information for the pods associated with the Group resource\ntype PodDetails struct {\n\tName            string\n\tStartTime       meta_v1.Time\n\tEndTime         meta_v1.Time\n\tContainers      []*Container\n\tPodVolumeClaims map[string]*PersistentVolumeClaim\n}\n\n// PersistentVolumeClaim information for the pods associated with the Group resource\n// A PVC can bound and unbound to a pod many times, so maintaining\n// BoundTimes and UnboundTimes as lists.\n// A PVC can be upgraded or downgraded, so maintaining capacityAllocated as a list\n// Whenever a PVC capacity changes will update UnboundTime for old capacity, and\n// append new capacity to capacityAllocated with bound time appended to BoundTimes\n// The i-th capacity allocated corresponds to the i-th bound time and to i-th unbound time.\n// Similarly for RequestSizeInGB\ntype PersistentVolumeClaim struct {\n\tName                  string\n\tVolumeName            string\n\tRequestSizeInGB       []float64\n\tCapacityAllocatedInGB []float64\n\tBoundTimes            []meta_v1.Time\n\tUnboundTimes          []meta_v1.Time\n}\n\n// Container information for the pods associated with the Group resource\ntype Container struct {\n\tName    string\n\tMetrics *metrics.Metrics\n}\n"
  },
  {
    "path": "pkg/apis/subscriber/v1/deepcopy.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport \"k8s.io/apimachinery/pkg/runtime\"\n\n// DeepCopyInto copies all properties of this object into another object of the\n// same type that is provided as a pointer.\nfunc (in *Subscriber) DeepCopyInto(out *Subscriber) {\n\tout.TypeMeta = in.TypeMeta\n\tout.ObjectMeta = in.ObjectMeta\n\tout.Spec = in.Spec\n\tout.Status = in.Status\n}\n\n// DeepCopyObject returns a generically typed copy of an object\nfunc (in *Subscriber) DeepCopyObject() runtime.Object {\n\tout := Subscriber{}\n\tin.DeepCopyInto(&out)\n\treturn &out\n}\n\n// DeepCopyObject returns a generically typed copy of an object\nfunc (in *SubscriberList) DeepCopyObject() runtime.Object {\n\tout := SubscriberList{}\n\tout.TypeMeta = in.TypeMeta\n\tout.ListMeta = in.ListMeta\n\n\tif in.Items != nil {\n\t\tout.Items = make([]Subscriber, len(in.Items))\n\t\tfor i := range in.Items {\n\t\t\tin.Items[i].DeepCopyInto(&out.Items[i])\n\t\t}\n\t}\n\treturn &out\n}\n"
  },
  {
    "path": "pkg/apis/subscriber/v1/docs.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n"
  },
  {
    "path": "pkg/apis/subscriber/v1/register.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\n// SchemeBuilder parameters\nvar (\n\tSchemeBuilder = runtime.NewSchemeBuilder(AddKnownTypes)\n\tAddToScheme   = SchemeBuilder.AddToScheme\n)\n\n// SubscriberGroupVersion is group version used to register these objects\nvar SubscriberGroupVersion = schema.GroupVersion{Group: SubscriberGroup, Version: SubscriberVersion}\n\n// Kind takes an unqualified kind and returns a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SubscriberGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SubscriberGroupVersion.WithResource(resource).GroupResource()\n}\n\n// AddKnownTypes ...\nfunc AddKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SubscriberGroupVersion,\n\t\t&Subscriber{},\n\t\t&SubscriberList{},\n\t)\n\tmeta_v1.AddToGroupVersion(scheme, SubscriberGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apis/subscriber/v1/types.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport meta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n// CRD Subscriber attributes\nconst (\n\tSubscriberPlural   string = \"subscribers\"\n\tSubscriberGroup    string = \"vmware.purser.com\"\n\tSubscriberVersion  string = \"v1\"\n\tSubscriberFullName string = SubscriberPlural + \".\" + SubscriberGroup\n)\n\n// Subscriber information\ntype Subscriber struct {\n\tmeta_v1.TypeMeta   `json:\",inline\"`\n\tmeta_v1.ObjectMeta `json:\"metadata\"`\n\tSpec               SubscriberSpec   `json:\"spec\"`\n\tStatus             SubscriberStatus `json:\"status,omitempty\"`\n}\n\n// SubscriberSpec definition details\ntype SubscriberSpec struct {\n\tName    string            `json:\"name\"`\n\tHeaders map[string]string `json:\"headers\"`\n\tURL     string            `json:\"url\"`\n}\n\n// SubscriberStatus definition\ntype SubscriberStatus struct {\n\tState   string `json:\"state,omitempty\"`\n\tMessage string `json:\"message,omitempty\"`\n}\n\n// SubscriberList type\ntype SubscriberList struct {\n\tmeta_v1.TypeMeta `json:\",inline\"`\n\tmeta_v1.ListMeta `json:\"metadata\"`\n\tItems            []Subscriber `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/client/clientset/typed/groups/v1/group.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\t\"github.com/vmware/purser/pkg/apis/groups/v1\"\n\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// GroupInterface has client methods we need to access Group object\ntype GroupInterface interface {\n\tCreate(obj *v1.Group) (*v1.Group, error)\n\tUpdate(obj *v1.Group) (*v1.Group, error)\n\tDelete(name string, options *meta_v1.DeleteOptions) error\n\tGet(name string) (*v1.Group, error)\n\tList(opts meta_v1.ListOptions) (*v1.GroupList, error)\n\tWatch(opts meta_v1.ListOptions) (watch.Interface, error)\n}\n\n// GroupClient defines the CRD Group structure\ntype GroupClient struct {\n\tclient *rest.RESTClient\n\tns     string\n\tplural string\n\tcodec  runtime.ParameterCodec\n}\n\n// Create creates a new group.\nfunc (c *GroupClient) Create(obj *v1.Group) (*v1.Group, error) {\n\tresult := v1.Group{}\n\terr := c.client.Post().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tBody(obj).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// Update modifies the group specification.\nfunc (c *GroupClient) Update(obj *v1.Group) (*v1.Group, error) {\n\tresult := v1.Group{}\n\terr := c.client.Put().\n\t\tName((obj.Name)).\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tBody(obj).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// Delete removes the group.\nfunc (c *GroupClient) Delete(name string, options *meta_v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tName(name).\n\t\tBody(options).\n\t\tDo().\n\t\tError()\n}\n\n// Get fetches the group\nfunc (c *GroupClient) Get(name string) (*v1.Group, error) {\n\tresult := v1.Group{}\n\terr := c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tName(name).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// List fetches the list of groups.\nfunc (c *GroupClient) List(opts meta_v1.ListOptions) (*v1.GroupList, error) {\n\tresult := v1.GroupList{}\n\terr := c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tVersionedParams(&opts, c.codec).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// Watch watches for the groups.\nfunc (c *GroupClient) Watch(opts meta_v1.ListOptions) (watch.Interface, error) {\n\topts.Watch = true\n\treturn c.client.\n\t\tGet().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tVersionedParams(&opts, c.codec).\n\t\tWatch()\n}\n"
  },
  {
    "path": "pkg/client/clientset/typed/groups/v1/group_client.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\t\"reflect\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\n\tapiextv1beta1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1\"\n\tapiextcs \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// NewGroupClient returns an instance of the Group Client\nfunc NewGroupClient(clientset apiextcs.Interface, config *rest.Config) *GroupClient {\n\terr := createGroupCRD(clientset)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create CRD group %v\", err)\n\t}\n\n\t// Wait for the CRD to be created before we use it (only needed if its a new one)\n\ttime.Sleep(3 * time.Second)\n\n\t// Create a new clientset which include our CRD schema\n\tgcrdcs, gscheme, err := newClient(config)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to add CRD group schema to clientset %v\", err)\n\t}\n\n\t// Create a CRD client interface\n\treturn Group(gcrdcs, gscheme, \"default\")\n}\n\n// Group returns a new instance of the Group CRD\nfunc Group(client *rest.RESTClient, scheme *runtime.Scheme, namespace string) *GroupClient {\n\treturn &GroupClient{\n\t\tclient: client,\n\t\tns:     namespace,\n\t\tplural: groups_v1.CRDPlural,\n\t\tcodec:  runtime.NewParameterCodec(scheme),\n\t}\n}\n\nfunc createGroupCRD(clientset apiextcs.Interface) error {\n\tcrd := &apiextv1beta1.CustomResourceDefinition{\n\t\tObjectMeta: meta_v1.ObjectMeta{Name: groups_v1.FullCRDName},\n\t\tSpec: apiextv1beta1.CustomResourceDefinitionSpec{\n\t\t\tGroup:   groups_v1.CRDGroup,\n\t\t\tVersion: groups_v1.CRDVersion,\n\t\t\t//TODO: make cluster scoped?\n\t\t\tScope: apiextv1beta1.NamespaceScoped,\n\t\t\tNames: apiextv1beta1.CustomResourceDefinitionNames{\n\t\t\t\tPlural: groups_v1.CRDPlural,\n\t\t\t\tKind:   reflect.TypeOf(groups_v1.Group{}).Name(),\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)\n\tif err != nil && apierrors.IsAlreadyExists(err) {\n\t\treturn nil\n\t}\n\treturn err\n}\n\nfunc newClient(cfg *rest.Config) (*rest.RESTClient, *runtime.Scheme, error) {\n\tconfig := *cfg\n\tscheme, err := setConfigDefaults(&config)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tclient, err := rest.RESTClientFor(&config)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn client, scheme, nil\n}\n\nfunc setConfigDefaults(config *rest.Config) (*runtime.Scheme, error) {\n\tscheme := runtime.NewScheme()\n\tSchemeBuilder := runtime.NewSchemeBuilder(groups_v1.AddKnownTypes)\n\tif err := SchemeBuilder.AddToScheme(scheme); err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.GroupVersion = &groups_v1.SchemeGroupVersion\n\tconfig.APIPath = \"/apis\"\n\tconfig.ContentType = runtime.ContentTypeJSON\n\tconfig.NegotiatedSerializer = serializer.DirectCodecFactory{\n\t\tCodecFactory: serializer.NewCodecFactory(scheme),\n\t}\n\treturn scheme, nil\n}\n"
  },
  {
    "path": "pkg/client/clientset/typed/subscriber/v1/subsciber_client.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\t\"reflect\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\tsubscriber_v1 \"github.com/vmware/purser/pkg/apis/subscriber/v1\"\n\n\tapiextv1beta1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1\"\n\tapiextcs \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// NewSubscriberClient returns an instance of the Subscriber Client\nfunc NewSubscriberClient(clientset apiextcs.Interface, config *rest.Config) *SubscriberClient {\n\terr := createSubscriberCRD(clientset)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create CRD subscriber %v\", err)\n\t}\n\n\t// Wait for the CRD to be created before we use it (only needed if its a new one)\n\ttime.Sleep(3 * time.Second)\n\n\t// Create a new clientset which include our CRD schema\n\tcrdcs, scheme, err := newClient(config)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to add CRD subscriber schema to clientset %v\", err)\n\t}\n\n\t// Create a CRD client interface\n\treturn Subscriber(crdcs, scheme, \"default\")\n}\n\n// Subscriber returns an instance of the subscriber client\nfunc Subscriber(client *rest.RESTClient, scheme *runtime.Scheme, namespace string) *SubscriberClient {\n\treturn &SubscriberClient{\n\t\tclient: client,\n\t\tns:     namespace,\n\t\tplural: subscriber_v1.SubscriberPlural,\n\t\tcodec:  runtime.NewParameterCodec(scheme),\n\t}\n}\n\nfunc createSubscriberCRD(clientset apiextcs.Interface) error {\n\tcrd := &apiextv1beta1.CustomResourceDefinition{\n\t\tObjectMeta: meta_v1.ObjectMeta{Name: subscriber_v1.SubscriberFullName},\n\t\tSpec: apiextv1beta1.CustomResourceDefinitionSpec{\n\t\t\tGroup:   subscriber_v1.SubscriberGroup,\n\t\t\tVersion: subscriber_v1.SubscriberVersion,\n\t\t\t//TODO: make cluster scoped?\n\t\t\tScope: apiextv1beta1.NamespaceScoped,\n\t\t\tNames: apiextv1beta1.CustomResourceDefinitionNames{\n\t\t\t\tPlural: subscriber_v1.SubscriberPlural,\n\t\t\t\tKind:   reflect.TypeOf(subscriber_v1.Subscriber{}).Name(),\n\t\t\t},\n\t\t},\n\t}\n\t_, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)\n\t// Ignore error if it already exists\n\tif err != nil && apierrors.IsAlreadyExists(err) {\n\t\treturn nil\n\t}\n\treturn err\n}\n\nfunc newClient(cfg *rest.Config) (*rest.RESTClient, *runtime.Scheme, error) {\n\tconfig := *cfg\n\tscheme, err := setConfigDefaults(&config)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tclient, err := rest.RESTClientFor(&config)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn client, scheme, nil\n}\n\nfunc setConfigDefaults(config *rest.Config) (*runtime.Scheme, error) {\n\tscheme := runtime.NewScheme()\n\tSchemeBuilder := runtime.NewSchemeBuilder(subscriber_v1.AddKnownTypes)\n\tif err := SchemeBuilder.AddToScheme(scheme); err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.GroupVersion = &subscriber_v1.SubscriberGroupVersion\n\tconfig.APIPath = \"/apis\"\n\tconfig.ContentType = runtime.ContentTypeJSON\n\tconfig.NegotiatedSerializer = serializer.DirectCodecFactory{\n\t\tCodecFactory: serializer.NewCodecFactory(scheme)}\n\treturn scheme, nil\n}\n"
  },
  {
    "path": "pkg/client/clientset/typed/subscriber/v1/subscriber.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1\n\nimport (\n\t\"github.com/vmware/purser/pkg/apis/subscriber/v1\"\n\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// SubscriberInterface has client methods we need to access Subscriber object\ntype SubscriberInterface interface {\n\tCreate(obj *v1.Subscriber) (*v1.Subscriber, error)\n\tUpdate(obj *v1.Subscriber) (*v1.Subscriber, error)\n\tDelete(name string, options *meta_v1.DeleteOptions) error\n\tGet(name string) (*v1.Subscriber, error)\n\tList(opts meta_v1.ListOptions) (*v1.SubscriberList, error)\n\tWatch(opts meta_v1.ListOptions) (watch.Interface, error)\n}\n\n// SubscriberClient structure\ntype SubscriberClient struct {\n\tclient *rest.RESTClient\n\tns     string\n\tplural string\n\tcodec  runtime.ParameterCodec\n}\n\n// Create creates a CRD subscriber.\nfunc (c *SubscriberClient) Create(obj *v1.Subscriber) (*v1.Subscriber, error) {\n\tresult := v1.Subscriber{}\n\terr := c.client.Post().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tBody(obj).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// Update modifies the subscriber.\nfunc (c *SubscriberClient) Update(obj *v1.Subscriber) (*v1.Subscriber, error) {\n\tresult := v1.Subscriber{}\n\terr := c.client.Put().\n\t\tName((obj.Name)).\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tBody(obj).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// Delete removes the subscriber.\nfunc (c *SubscriberClient) Delete(name string, options *meta_v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tName(name).\n\t\tBody(options).\n\t\tDo().\n\t\tError()\n}\n\n// Get returns the subscriber\nfunc (c *SubscriberClient) Get(name string) (*v1.Subscriber, error) {\n\tresult := v1.Subscriber{}\n\terr := c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tName(name).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// List fetches the list of subscriber CRD clients.\nfunc (c *SubscriberClient) List(opts meta_v1.ListOptions) (*v1.SubscriberList, error) {\n\tresult := v1.SubscriberList{}\n\terr := c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tVersionedParams(&opts, c.codec).\n\t\tDo().\n\t\tInto(&result)\n\treturn &result, err\n}\n\n// Watch watches for the subcriber CRD\nfunc (c *SubscriberClient) Watch(opts meta_v1.ListOptions) (watch.Interface, error) {\n\topts.Watch = true\n\treturn c.client.\n\t\tGet().\n\t\tNamespace(c.ns).\n\t\tResource(c.plural).\n\t\tVersionedParams(&opts, c.codec).\n\t\tWatch()\n}\n"
  },
  {
    "path": "pkg/client/clientset.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage client\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/vmware/purser/pkg/utils\"\n\n\tapiextcs \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// GetAPIExtensionClient returns a client for the cluster and it's config.\nfunc GetAPIExtensionClient(kubeconfigPath string) (*apiextcs.Clientset, *rest.Config) {\n\tconfig, err := utils.GetKubeconfig(kubeconfigPath)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to fetch kubeconfig %v\", err)\n\t}\n\n\t// create clientset and create our CRD, this only need to run once\n\tclientset, clientErr := apiextcs.NewForConfig(config)\n\tif clientErr != nil {\n\t\tlog.Fatalf(\"failed to connect to the cluster %v\", clientErr)\n\t}\n\n\treturn clientset, config\n}\n"
  },
  {
    "path": "pkg/controller/buffering/ring_buffer.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage buffering\n\nimport (\n\t\"sync\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\n// BufferSize the default size for the Ring Buffer\nconst BufferSize uint32 = 5000\n\n// RingBuffer data structure\ntype RingBuffer struct {\n\tstart, end, Size uint32\n\tbuffer           [BufferSize]*interface{}\n\tMutex            *sync.Mutex\n}\n\n// Put adds the item into buffer if there is room in buffer.\n// Returns true if item is buffered otherwise false.\nfunc (r *RingBuffer) Put(inp interface{}) bool {\n\tr.Mutex.Lock()\n\tdefer r.Mutex.Unlock()\n\n\tif r.isFull() {\n\t\treturn false\n\t}\n\n\tnext := next(r.end, r.Size)\n\tr.buffer[r.end] = &inp\n\tr.end = next\n\treturn true\n}\n\n// Get returns the elements in FIFO manner or nil if buffer is empty.\nfunc (r *RingBuffer) Get() *interface{} {\n\tr.Mutex.Lock()\n\tdefer r.Mutex.Unlock()\n\n\tif r.isEmpty() {\n\t\treturn nil\n\t}\n\n\tnext := next(r.start, r.Size)\n\tcurval := r.buffer[r.start]\n\tr.buffer[r.start] = nil\n\tr.start = next\n\treturn curval\n}\n\n// ReadN reads the next n available elements in the buffer.\n// Returns elements and number of elements read.\nfunc (r *RingBuffer) ReadN(n uint32) ([]*interface{}, uint32) {\n\tr.Mutex.Lock()\n\tdefer r.Mutex.Unlock()\n\n\tvar elements []*interface{}\n\n\tstart := r.start\n\tfor i := uint32(0); i < n; i++ {\n\t\tif start == r.end {\n\t\t\tbreak\n\t\t}\n\t\telements = append(elements, r.buffer[start])\n\t\tstart = next(start, r.Size)\n\t}\n\treturn elements, uint32(len(elements))\n}\n\n// RemoveN removes the first n elements from the buffer.\nfunc (r *RingBuffer) RemoveN(n uint32) {\n\tr.Mutex.Lock()\n\tdefer r.Mutex.Unlock()\n\n\tstart := r.start\n\tfor i := uint32(0); i < n; i++ {\n\t\tif start == r.end {\n\t\t\tbreak\n\t\t}\n\t\tr.buffer[start] = nil\n\t\tstart = next(start, r.Size)\n\t\tr.start = start\n\t}\n}\n\nfunc (r *RingBuffer) isEmpty() bool {\n\treturn r.start == r.end\n}\n\nfunc (r *RingBuffer) isFull() bool {\n\treturn next(r.end, r.Size) == r.start\n}\n\nfunc next(cur uint32, size uint32) uint32 {\n\treturn (cur + 1) % size\n}\n\n// PrintDetails diplays details for debugging purpose.\nfunc (r *RingBuffer) PrintDetails() {\n\tlog.Debugf(\"Start Position = %d, End Position = %d, Buffer Size = %d\", r.start, r.end, r.Size)\n}\n"
  },
  {
    "path": "pkg/controller/controller.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\tsubscriber_v1 \"github.com/vmware/purser/pkg/apis/subscriber/v1\"\n\n\tapps_v1beta1 \"k8s.io/api/apps/v1beta1\"\n\tbatch_v1 \"k8s.io/api/batch/v1\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n\text_v1beta1 \"k8s.io/api/extensions/v1beta1\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\n// Kubeclient is kubernetes Clientset\nvar Kubeclient *kubernetes.Clientset\n\n// Controller holds Kubernetes controller components\ntype Controller struct {\n\tclientset kubernetes.Interface\n\tqueue     workqueue.RateLimitingInterface\n\tinformer  cache.SharedIndexInformer\n\tconf      *Config\n}\n\n// Event indicate the informerEvent\ntype Event struct {\n\tkey          string\n\teventType    string\n\tresourceType string\n\tdata         interface{}\n\tcaptureTime  meta_v1.Time\n}\n\n// Start runs the controller goroutine.\n// nolint: gocyclo, interfacer\nfunc Start(conf *Config) {\n\tKubeclient = conf.Kubeclient\n\n\tif conf.Resource.Pod {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Pods(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Pods(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&api_v1.Pod{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Pod\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Node {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Nodes().List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Nodes().Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&api_v1.Node{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Node\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.PersistentVolume {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().PersistentVolumes().List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().PersistentVolumes().Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&api_v1.PersistentVolume{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"PersistentVolume\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.PersistentVolumeClaim {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().PersistentVolumeClaims(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().PersistentVolumeClaims(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&api_v1.PersistentVolumeClaim{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"PersistentVolumeClaim\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Service {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Services(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Services(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&api_v1.Service{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Service\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.ReplicaSet {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.ExtensionsV1beta1().ReplicaSets(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.ExtensionsV1beta1().ReplicaSets(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&ext_v1beta1.ReplicaSet{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"ReplicaSet\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.DaemonSet {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.ExtensionsV1beta1().DaemonSets(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.ExtensionsV1beta1().DaemonSets(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&ext_v1beta1.DaemonSet{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"DaemonSet\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Deployment {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.AppsV1beta1().Deployments(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.AppsV1beta1().Deployments(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&apps_v1beta1.Deployment{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Deployment\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.StatefulSet {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.AppsV1beta1().StatefulSets(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.AppsV1beta1().StatefulSets(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&apps_v1beta1.StatefulSet{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"StatefulSet\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Job {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.BatchV1().Jobs(meta_v1.NamespaceAll).List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.BatchV1().Jobs(meta_v1.NamespaceAll).Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&batch_v1.Job{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Job\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Namespace {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Namespaces().List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn Kubeclient.CoreV1().Namespaces().Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&api_v1.Namespace{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Namespace\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Group {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn conf.Groupcrdclient.List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn conf.Groupcrdclient.Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&groups_v1.Group{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Group\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tif conf.Resource.Subscriber {\n\t\tinformer := cache.NewSharedIndexInformer(\n\t\t\t&cache.ListWatch{\n\t\t\t\tListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {\n\t\t\t\t\treturn conf.Subscriberclient.List(options)\n\t\t\t\t},\n\t\t\t\tWatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {\n\t\t\t\t\treturn conf.Subscriberclient.Watch(options)\n\t\t\t\t},\n\t\t\t},\n\t\t\t&subscriber_v1.Subscriber{},\n\t\t\t0,\n\t\t\tcache.Indexers{},\n\t\t)\n\n\t\tc := newResourceController(Kubeclient, informer, \"Subscriber\")\n\t\tc.conf = conf\n\t\tstopCh := make(chan struct{})\n\t\tdefer close(stopCh)\n\n\t\tgo c.Run(stopCh)\n\t}\n\n\tsigterm := make(chan os.Signal, 1)\n\tsignal.Notify(sigterm, syscall.SIGTERM)\n\tsignal.Notify(sigterm, syscall.SIGINT)\n\t<-sigterm\n}\n\nfunc newResourceController(client kubernetes.Interface, informer cache.SharedIndexInformer, resourceType string) *Controller {\n\tqueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())\n\tvar newEvent Event\n\tvar err error\n\tinformer.AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\tnewEvent.key, err = cache.MetaNamespaceKeyFunc(obj)\n\t\t\tnewEvent.eventType = Create\n\t\t\tnewEvent.resourceType = resourceType\n\t\t\tnewEvent.captureTime = meta_v1.Now()\n\t\t\tlog.Printf(\"Processing add to %v: %s\", resourceType, newEvent.key)\n\t\t\tif err == nil {\n\t\t\t\tqueue.Add(newEvent)\n\t\t\t}\n\t\t},\n\t\t// TODO: Fixme\n\t\tUpdateFunc: func(old, new interface{}) {\n\t\t\t/*newEvent.key, err = cache.MetaNamespaceKeyFunc(old)\n\t\t\tnewEvent.eventType = \"update\"\n\t\t\tnewEvent.resourceType = resourceType\n\t\t\tlog.Printf(\"Processing update to %v: %s\", resourceType, newEvent.key)\n\t\t\tif err == nil {\n\t\t\t\tqueue.Add(newEvent)\n\t\t\t}*/\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tnewEvent.key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj)\n\t\t\tnewEvent.eventType = Delete\n\t\t\tnewEvent.resourceType = resourceType\n\t\t\tnewEvent.data = obj\n\t\t\tnewEvent.captureTime = meta_v1.Now()\n\t\t\tlog.Printf(\"Processing delete to %v: %s\", resourceType, newEvent.key)\n\t\t\tif err == nil {\n\t\t\t\tqueue.Add(newEvent)\n\t\t\t}\n\t\t},\n\t})\n\n\treturn &Controller{\n\t\tclientset: client,\n\t\tinformer:  informer,\n\t\tqueue:     queue,\n\t}\n}\n\n// Run initiates the controller\nfunc (c *Controller) Run(stopCh <-chan struct{}) {\n\tdefer utilruntime.HandleCrash()\n\tdefer c.queue.ShutDown()\n\n\tgo c.informer.Run(stopCh)\n\n\tif !cache.WaitForCacheSync(stopCh, c.HasSynced) {\n\t\tutilruntime.HandleError(fmt.Errorf(\"timed out waiting for caches to sync\"))\n\t\treturn\n\t}\n\n\tlog.Println(\"Purser controller synced and ready\")\n\twait.Until(c.runWorker, time.Second, stopCh)\n}\n\n// HasSynced is required for the cache.Controller interface.\nfunc (c *Controller) HasSynced() bool {\n\treturn c.informer.HasSynced()\n}\n\n// LastSyncResourceVersion is required for the cache.Controller interface.\nfunc (c *Controller) LastSyncResourceVersion() string {\n\treturn c.informer.LastSyncResourceVersion()\n}\n\nfunc (c *Controller) runWorker() {\n\tfor c.processNextItem() {\n\t\t// continue looping\n\t}\n}\n\nfunc (c *Controller) processNextItem() bool {\n\tnewEvent, quit := c.queue.Get()\n\n\tif quit {\n\t\treturn false\n\t}\n\tdefer c.queue.Done(newEvent)\n\terr := c.processItem(newEvent.(Event))\n\tif err == nil {\n\t\tc.queue.Forget(newEvent)\n\t} else {\n\t\tlog.Printf(\"Error processing %s (giving up): %v\", newEvent.(Event).key, err)\n\t\tc.queue.Forget(newEvent)\n\t\tutilruntime.HandleError(err)\n\t}\n\n\treturn true\n}\n\nfunc (c *Controller) processItem(newEvent Event) error {\n\tobj, _, err := c.informer.GetIndexer().GetByKey(newEvent.key)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error fetching object with key %s from store: %v\", newEvent.key, err)\n\t}\n\n\t// process events based on its type\n\tswitch newEvent.eventType {\n\tcase Create:\n\t\tstr, err := json.Marshal(obj)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Error marshalling object %s\", obj)\n\t\t}\n\t\tpayload := &Payload{Key: newEvent.key, EventType: newEvent.eventType, ResourceType: newEvent.resourceType,\n\t\t\tCloudType: \"aws\", Data: string(str), CaptureTime: newEvent.captureTime}\n\t\tc.conf.RingBuffer.Put(payload)\n\t\treturn nil\n\tcase Update:\n\t\t// TODO: Decide on what needs to be propagated.\n\t\treturn nil\n\tcase Delete:\n\t\tstr, err := json.Marshal(newEvent.data)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Error marshalling object %s\", newEvent.data)\n\t\t}\n\t\tpayload := &Payload{Key: newEvent.key, EventType: newEvent.eventType, ResourceType: newEvent.resourceType,\n\t\t\tCloudType: \"aws\", Data: string(str), CaptureTime: newEvent.captureTime}\n\t\tc.conf.RingBuffer.Put(payload)\n\t\treturn nil\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/controller_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"testing\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/client\"\n\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tsubscriber_v1 \"github.com/vmware/purser/pkg/client/clientset/typed/subscriber/v1\"\n)\n\n// TestCrdFlow executes the CRD flow.\nfunc TestCrdFlow(t *testing.T) {\n\tclientset, clusterConfig := client.GetAPIExtensionClient(\"\")\n\tsubcrdclient := subscriber_v1.NewSubscriberClient(clientset, clusterConfig)\n\tListSubscriberCrdInstances(subcrdclient)\n\n\tsigterm := make(chan os.Signal, 1)\n\tsignal.Notify(sigterm, syscall.SIGTERM)\n\tsignal.Notify(sigterm, syscall.SIGINT)\n\t<-sigterm\n}\n\n// ListSubscriberCrdInstances fetches list of subscriber CRD instances.\nfunc ListSubscriberCrdInstances(crdclient *subscriber_v1.SubscriberClient) {\n\titems, err := crdclient.List(meta_v1.ListOptions{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Printf(\"List:\\n%v\\n\", items)\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/dgraph.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage dgraph\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/dgraph-io/dgo\"\n\t\"github.com/dgraph-io/dgo/protos/api\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\t\"google.golang.org/grpc\"\n)\n\n// mutation types\nconst (\n\tCREATE = \"create\"\n\tUPDATE = \"update\"\n\tDELETE = \"delete\"\n)\n\n// Dgraph variables\nvar (\n\tclient     *dgo.Dgraph\n\tconnection *grpc.ClientConn\n)\n\n// ID maps the external ID used in Dgraph to the UID\ntype ID struct {\n\tXid string `json:\"xid,omitempty\"`\n\tUID string `json:\"uid,omitempty\"`\n}\n\n// Start opens and creates schema in dgraph\nfunc Start(url string, port string) {\n\terr := Open(url + \":\" + port)\n\tif err != nil {\n\t\tlog.Errorf(\"error while opening connection to Dgraph: %v\", err)\n\t}\n\n\terr = CreateSchema()\n\tif err != nil {\n\t\tlog.Errorf(\"error while creating schema: %v\", err)\n\t}\n}\n\n// Open creates and establishes a new Dgraph connection\nfunc Open(url string) error {\n\tconn, err := grpc.Dial(url, grpc.WithInsecure())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconnection = conn\n\tdc := api.NewDgraphClient(connection)\n\tclient = dgo.NewDgraphClient(dc)\n\n\treturn nil\n}\n\n// Close terminates the Dgraph connection\nfunc Close() {\n\terr := connection.Close()\n\tif err != nil {\n\t\tfmt.Println(\"Error closing connection to Dgraph \", err)\n\t}\n}\n\n// CreateSchema sets the Dgraph schema\nfunc CreateSchema() error {\n\top := &api.Operation{}\n\top.Schema = `\n\t\tname: string @index(term) .\n\t\tusername: string @index(term) .\n\t\txid:  string @index(term) .\n\t\tstartTime: dateTime @index(hour) .\n\t\tendTime: dateTime @index(hour) .\n\t\tisService: bool .\n\t\tisPod: bool .\n\t\tisContainer: bool .\n\t\tisProc: bool .\n\t\tisGroup: bool .\n\t\tisNodePrice: bool .\n\t\tisStoragePrice: bool .\n\t\tisRateCard: bool .\n        isLogin: bool .\n\t\tpod: uid @reverse .\n\t\tnamespace: uid @reverse .\n\t\tdeployment: uid @reverse .\n\t\treplicaset: uid @reverse .\n\t\tstatefulset: uid @reverse .\n\t\tcontainer: uid @reverse .\n\t\tservice: uid @reverse .\n\t\tnode: uid @reverse .\n\t\tpv: uid @reverse .\n\t\tdaemonset: uid @reverse .\n\t\tjob: uid @reverse .\n\t\tlabel: uid @reverse .\n\t\tkey: string @index(term) .\n\t\tvalue: string @index(term) .\n\t\tcpu: float .\n\t\tcpuRequest: float .\n\t\tcpuLimit: float .\n\t\tcpuCapacity: float .\n\t\tcpuPrice: float .\n\t\tmemory: float .\n\t\tmemoryRequest: float .\n\t\tmemoryLimit: float .\n\t\tmemoryCapacity: float .\n\t\tmemoryPrice: float .\n\t\tstorage: float .\n\t\tstorageRequest: float .\n\t\tstorageLimit: float .\n\t\tstorageCapacity: float .\n\t\tstoragePrice: float .\n\t\tmtdCPU: float .\n\t\tmtdCPUCost: float .\n\t\tmtdCost: float .\n\t\tmtdMemory: float .\n\t\tmtdMemoryCost: float .\n\t\tprice: float .\n\t\tpodsCount: int .\n\t`\n\tctx := context.Background()\n\terr := client.Alter(ctx, op)\n\n\treturn err\n}\n\n// GetUID returns the UID of the node in the Dgraph\n// returns empty string if error has occurred\nfunc GetUID(id string, nodeType string) string {\n\tquery := `query Me($id:string, $nodeType:string) {\n\t\tgetUid(func: eq(xid, $id)) @filter(has(` + nodeType + `)) {\n\t\t\tuid\n\t\t}\n\t}`\n\n\tctx := context.Background()\n\tvariables := make(map[string]string)\n\tvariables[\"$nodeType\"] = nodeType\n\tvariables[\"$id\"] = id\n\n\tresp, err := client.NewReadOnlyTxn().QueryWithVars(ctx, query, variables)\n\tif err != nil {\n\t\tlog.Printf(\"failed to fetch UID from Dgraph %v\", err)\n\t\treturn \"\"\n\t}\n\treturn unmarshalDgraphResponse(resp, id)\n}\n\n// ExecuteQueryRaw given a query and it fetches and writes result into interface\nfunc ExecuteQueryRaw(query string) ([]byte, error) {\n\tlog.Debugf(\"query: (%v)\", query)\n\tctx := context.Background()\n\n\tresp, err := client.NewTxn().Query(ctx, query)\n\tif err != nil {\n\t\tlog.Error(err)\n\t\treturn nil, err\n\t}\n\treturn resp.Json, nil\n}\n\n// ExecuteQuery given a query and it fetches and writes result into interface\nfunc ExecuteQuery(query string, root interface{}) error {\n\trespJSON, err := ExecuteQueryRaw(query)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = json.Unmarshal(respJSON, root)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// MutateNode mutates a Dgraph transaction\nfunc MutateNode(data interface{}, mutateType string) (*api.Assigned, error) {\n\tbytes := utils.JSONMarshal(data)\n\tif bytes == nil {\n\t\treturn nil, fmt.Errorf(\"unable to marshal data: %v\", data)\n\t}\n\n\tmu := &api.Mutation{\n\t\tCommitNow: true,\n\t}\n\tswitch mutateType {\n\tcase DELETE:\n\t\tmu.DeleteJson = bytes\n\tdefault:\n\t\tmu.SetJson = bytes\n\t}\n\n\tctx := context.Background()\n\treturn client.NewTxn().Mutate(ctx, mu)\n}\n\n// unmarshalDgraphResponse returns empty string if error has occurred\nfunc unmarshalDgraphResponse(resp *api.Response, id string) string {\n\ttype Root struct {\n\t\tIDs []ID `json:\"getUid\"`\n\t}\n\n\tvar r Root\n\terr := json.Unmarshal(resp.Json, &r)\n\tif err != nil {\n\t\tlog.Debugf(\"failed to marshal Dgraph response %v\", err)\n\t\treturn \"\"\n\t}\n\n\tif len(r.IDs) == 0 {\n\t\tlog.Debugf(\"id %s is not in dgraph\", id)\n\t\treturn \"\"\n\t}\n\n\treturn r.IDs[0].UID\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/login.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage dgraph\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\n// Login structure\ntype Login struct {\n\tID\n\tIsLogin  bool   `json:\"isLogin,omitempty\"`\n\tUsername string `json:\"username,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\n// Login constants\nconst (\n\tDefaultUsername = \"admin\"\n\tDefaultPassword = \"purser!123\"\n\tDefaultLoginXID = \"purser-login-xid\"\n\tIsLogin         = \"isLogin\"\n)\n\n// StoreLogin ...\nfunc StoreLogin() {\n\tuid := GetUID(DefaultLoginXID, IsLogin)\n\tif uid == \"\" {\n\t\thashedPassword, err := bcrypt.GenerateFromPassword([]byte(DefaultPassword), bcrypt.MinCost)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"error while hashing login information\")\n\t\t}\n\t\tlogin := Login{\n\t\t\tID:       ID{Xid: DefaultLoginXID},\n\t\t\tIsLogin:  true,\n\t\t\tUsername: DefaultUsername,\n\t\t\tPassword: string(hashedPassword),\n\t\t}\n\t\t_, err = MutateNode(login, CREATE)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"error while storing login information\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/constants.go",
    "content": "package models\n\n// Cost and other cloud constants\nconst (\n\t// Cost constants\n\tDefaultCPUCostPerCPUPerHour    = \"0.024\"\n\tDefaultMemCostPerGBPerHour     = \"0.01\"\n\tDefaultStorageCostPerGBPerHour = \"0.00013888888\"\n\tDefaultCPUCostInFloat64        = 0.024\n\tDefaultMemCostInFloat64        = 0.01\n\tDefaultStorageCostInFloat64    = 0.00013888888\n\n\t// Cloud provider constants\n\tAWS = \"aws\"\n\n\t// Time constants\n\tHoursInMonth = 720\n\n\t// Other constants\n\tPriceError = -1.0\n)\n"
  },
  {
    "path": "pkg/controller/dgraph/models/container.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/dgraph-io/dgo/protos/api\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsContainer = \"isContainer\"\n)\n\n// Container schema in dgraph\ntype Container struct {\n\tdgraph.ID\n\tIsContainer   bool       `json:\"isContainer,omitempty\"`\n\tName          string     `json:\"name,omitempty\"`\n\tStartTime     string     `json:\"startTime,omitempty\"`\n\tEndTime       string     `json:\"endTime,omitempty\"`\n\tPod           Pod        `json:\"pod,omitempty\"`\n\tProcs         []*Proc    `json:\"procs,omitempty\"`\n\tNamespace     *Namespace `json:\"namespace,omitempty\"`\n\tCPURequest    float64    `json:\"cpuRequest,omitempty\"`\n\tCPULimit      float64    `json:\"cpuLimit,omitempty\"`\n\tMemoryRequest float64    `json:\"memoryRequest,omitempty\"`\n\tMemoryLimit   float64    `json:\"memoryLimit,omitempty\"`\n\tType          string     `json:\"type,omitempty\"`\n}\n\nfunc newContainer(container api_v1.Container, podUID, namespaceUID string, pod api_v1.Pod) (*api.Assigned, error) {\n\tcontainerXid := pod.Namespace + \":\" + pod.Name + \":\" + container.Name\n\trequests := container.Resources.Requests\n\tlimits := container.Resources.Limits\n\tc := &Container{\n\t\tID:            dgraph.ID{Xid: containerXid},\n\t\tName:          \"container-\" + container.Name,\n\t\tIsContainer:   true,\n\t\tType:          \"container\",\n\t\tStartTime:     pod.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t\tPod:           Pod{ID: dgraph.ID{UID: podUID, Xid: pod.Namespace + \":\" + pod.Name}},\n\t\tCPURequest:    utils.ConvertToFloat64CPU(requests.Cpu()),\n\t\tCPULimit:      utils.ConvertToFloat64CPU(limits.Cpu()),\n\t\tMemoryRequest: utils.ConvertToFloat64GB(requests.Memory()),\n\t\tMemoryLimit:   utils.ConvertToFloat64GB(limits.Memory()),\n\t}\n\tif namespaceUID != \"\" {\n\t\tc.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: pod.Namespace}}\n\t}\n\treturn dgraph.MutateNode(c, dgraph.CREATE)\n}\n\n// StoreAndRetrieveContainersAndMetrics fetchs the list of containers in given pod\n// Create a new container in dgraph if container is not in it.\nfunc StoreAndRetrieveContainersAndMetrics(pod api_v1.Pod, podUID, namespaceUID string) ([]*Container, Metrics) {\n\tcontainers := []*Container{}\n\tcpuRequest := &resource.Quantity{}\n\tmemoryRequest := &resource.Quantity{}\n\tcpuLimit := &resource.Quantity{}\n\tmemoryLimit := &resource.Quantity{}\n\n\tfor _, c := range pod.Spec.Containers {\n\t\tcontainer, err := storeContainerIfNotExist(c, pod, podUID, namespaceUID)\n\t\tif err == nil {\n\t\t\tcontainers = append(containers, container)\n\t\t}\n\t\trequests := c.Resources.Requests\n\t\tlimits := c.Resources.Limits\n\t\tutils.AddResourceAToResourceB(requests.Cpu(), cpuRequest)\n\t\tutils.AddResourceAToResourceB(requests.Memory(), memoryRequest)\n\t\tutils.AddResourceAToResourceB(limits.Cpu(), cpuLimit)\n\t\tutils.AddResourceAToResourceB(limits.Memory(), memoryLimit)\n\t}\n\treturn containers, Metrics{\n\t\tCPURequest:    utils.ConvertToFloat64CPU(cpuRequest),\n\t\tCPULimit:      utils.ConvertToFloat64CPU(cpuLimit),\n\t\tMemoryRequest: utils.ConvertToFloat64GB(memoryRequest),\n\t\tMemoryLimit:   utils.ConvertToFloat64GB(memoryLimit),\n\t}\n}\n\n// StoreContainerProcessEdge ...\nfunc StoreContainerProcessEdge(containerXID string, procsXIDs []string) error {\n\tcontainerUID := dgraph.GetUID(containerXID, IsContainer)\n\tif containerUID == \"\" {\n\t\treturn fmt.Errorf(\"container: %s not persisted in dgraph\", containerXID)\n\t}\n\n\tprocs := retrieveProcessesFromProcessesXIDs(procsXIDs)\n\tcontainer := Container{\n\t\tID:    dgraph.ID{UID: containerUID, Xid: containerXID},\n\t\tProcs: procs,\n\t}\n\t_, err := dgraph.MutateNode(container, dgraph.UPDATE)\n\treturn err\n}\n\nfunc storeContainerIfNotExist(c api_v1.Container, pod api_v1.Pod, podUID, namespaceUID string) (*Container, error) {\n\tpodXid := pod.Namespace + \":\" + pod.Name\n\tcontainerXid := podXid + \":\" + c.Name\n\tcontainerUID := dgraph.GetUID(containerXid, IsContainer)\n\n\tvar container *Container\n\tif containerUID == \"\" {\n\t\tassigned, err := newContainer(c, podUID, namespaceUID, pod)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Unable to create container: %s\", containerXid)\n\t\t\treturn container, err\n\t\t}\n\t\tlog.Infof(\"Container with xid: (%s) persisted in dgraph\", containerXid)\n\t\tcontainerUID = assigned.Uids[\"blank-0\"]\n\t}\n\n\tcontainer = &Container{\n\t\tID: dgraph.ID{UID: containerUID, Xid: containerXid},\n\t}\n\treturn container, nil\n}\n\nfunc deleteContainersInTerminatedPod(containers []*Container, endTime time.Time) {\n\tfor _, container := range containers {\n\t\tcontainer.EndTime = endTime.Format(time.RFC3339)\n\t\tcontainer.Xid += container.EndTime\n\t\tcontainer.Name += \"*\" + container.EndTime // * in name indicates dead resources\n\t}\n\t_, err := dgraph.MutateNode(containers, dgraph.UPDATE)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\tdeleteProcessesInTerminatedContainers(containers)\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/daemonset.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\text_v1beta1 \"k8s.io/api/extensions/v1beta1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsDaemonset = \"isDaemonset\"\n)\n\n// Daemonset schema in dgraph\ntype Daemonset struct {\n\tdgraph.ID\n\tIsDaemonset bool       `json:\"isDaemonset,omitempty\"`\n\tName        string     `json:\"name,omitempty\"`\n\tStartTime   string     `json:\"startTime,omitempty\"`\n\tEndTime     string     `json:\"endTime,omitempty\"`\n\tNamespace   *Namespace `json:\"namespace,omitempty\"`\n\tPods        []*Pod     `json:\"pod,omitempty\"`\n\tType        string     `json:\"type,omitempty\"`\n}\n\nfunc createDaemonsetObject(daemonset ext_v1beta1.DaemonSet) Daemonset {\n\tnewDaemonset := Daemonset{\n\t\tName:        \"daemonset-\" + daemonset.Name,\n\t\tIsDaemonset: true,\n\t\tType:        \"daemonset\",\n\t\tID:          dgraph.ID{Xid: daemonset.Namespace + \":\" + daemonset.Name},\n\t\tStartTime:   daemonset.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(daemonset.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewDaemonset.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: daemonset.Namespace}}\n\t}\n\tdaemonsetDeletionTimestamp := daemonset.GetDeletionTimestamp()\n\tif !daemonsetDeletionTimestamp.IsZero() {\n\t\tnewDaemonset.EndTime = daemonsetDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewDaemonset.Xid += newDaemonset.EndTime\n\t\tnewDaemonset.Name += \"*\" + newDaemonset.EndTime\n\t}\n\treturn newDaemonset\n}\n\n// StoreDaemonset create a new daemonset in the Dgraph and updates if already present.\nfunc StoreDaemonset(daemonset ext_v1beta1.DaemonSet) (string, error) {\n\txid := daemonset.Namespace + \":\" + daemonset.Name\n\tuid := dgraph.GetUID(xid, IsDaemonset)\n\n\tnewDaemonset := createDaemonsetObject(daemonset)\n\tif uid != \"\" {\n\t\tnewDaemonset.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newDaemonset, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// CreateOrGetDaemonsetByID returns the uid of namespace if exists,\n// otherwise creates the daemonset and returns uid.\nfunc CreateOrGetDaemonsetByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsDaemonset)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := Daemonset{\n\t\tID:          dgraph.ID{Xid: xid},\n\t\tName:        xid,\n\t\tIsDaemonset: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/deployment.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\t\"log\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\tapps_v1beta1 \"k8s.io/api/apps/v1beta1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsDeployment = \"isDeployment\"\n)\n\n// Deployment schema in dgraph\ntype Deployment struct {\n\tdgraph.ID\n\tIsDeployment bool       `json:\"isDeployment,omitempty\"`\n\tName         string     `json:\"name,omitempty\"`\n\tStartTime    string     `json:\"startTime,omitempty\"`\n\tEndTime      string     `json:\"endTime,omitempty\"`\n\tNamespace    *Namespace `json:\"namespace,omitempty\"`\n\tPods         []*Pod     `json:\"pod,omitempty\"`\n\tType         string     `json:\"type,omitempty\"`\n}\n\nfunc createDeploymentObject(deployment apps_v1beta1.Deployment) Deployment {\n\tnewDeployment := Deployment{\n\t\tName:         \"deployment-\" + deployment.Name,\n\t\tIsDeployment: true,\n\t\tType:         \"deployment\",\n\t\tID:           dgraph.ID{Xid: deployment.Namespace + \":\" + deployment.Name},\n\t\tStartTime:    deployment.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(deployment.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewDeployment.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: deployment.Namespace}}\n\t}\n\tdeploymentDeletionTimestamp := deployment.GetDeletionTimestamp()\n\tif !deploymentDeletionTimestamp.IsZero() {\n\t\tnewDeployment.EndTime = deploymentDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewDeployment.Xid += newDeployment.EndTime\n\t\tnewDeployment.Name += \"*\" + newDeployment.EndTime\n\t}\n\treturn newDeployment\n}\n\n// StoreDeployment create a new deployment in the Dgraph and updates if already present.\nfunc StoreDeployment(deployment apps_v1beta1.Deployment) (string, error) {\n\txid := deployment.Namespace + \":\" + deployment.Name\n\tuid := dgraph.GetUID(xid, IsDeployment)\n\n\tnewDeployment := createDeploymentObject(deployment)\n\tif uid != \"\" {\n\t\tnewDeployment.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newDeployment, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// CreateOrGetDeploymentByID returns the uid of namespace if exists,\n// otherwise creates the deployment and returns uid.\nfunc CreateOrGetDeploymentByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsDeployment)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := Deployment{\n\t\tID:           dgraph.ID{Xid: xid},\n\t\tName:         xid,\n\t\tIsDeployment: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/group.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/dgraph-io/dgo/protos/api\"\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n)\n\n// Group constants\nconst (\n\tIsGroup        = \"isGroup\"\n\tgroupXIDPrefix = \"purser-group-\"\n)\n\n// Group schema in dgraph\ntype Group struct {\n\tdgraph.ID\n\tIsGroup                  bool    `json:\"isGroup,omitempty\"`\n\tName                     string  `json:\"name,omitempty\"`\n\tPodsCount                int     `json:\"podsCount,omitempty\"`\n\tMtdCPU                   float64 `json:\"mtdCPU,omitempty\"`\n\tMtdMemory                float64 `json:\"mtdMemory,omitempty\"`\n\tMtdStorage               float64 `json:\"mtdStorage,omitempty\"`\n\tCPU                      float64 `json:\"cpu,omitempty\"`\n\tMemory                   float64 `json:\"memory,omitempty\"`\n\tStorage                  float64 `json:\"storage,omitempty\"`\n\tMtdCPUCost               float64 `json:\"mtdCPUCost,omitempty\"`\n\tMtdMemoryCost            float64 `json:\"mtdMemoryCost,omitempty\"`\n\tMtdStorageCost           float64 `json:\"mtdStorageCost,omitempty\"`\n\tMtdCost                  float64 `json:\"mtdCost,omitempty\"`\n\tProjectedCPUCost         float64 `json:\"projectedCPUCost,omitempty\"`\n\tProjectedMemoryCost      float64 `json:\"projectedMemoryCost,omitempty\"`\n\tProjectedStorageCost     float64 `json:\"projectedStorageCost,omitempty\"`\n\tProjectedCost            float64 `json:\"projectedCost,omitempty\"`\n\tLastMonthCPUCost         float64 `json:\"lastMonthCPUCost,omitempty\"`\n\tLastMonthMemoryCost      float64 `json:\"lastMonthMemoryCost,omitempty\"`\n\tLastMonthStorageCost     float64 `json:\"lastMonthStorageCost,omitempty\"`\n\tLastMonthCost            float64 `json:\"lastMonthCost,omitempty\"`\n\tLastLastMonthCPUCost     float64 `json:\"lastLastMonthCPUCost,omitempty\"`\n\tLastLastMonthMemoryCost  float64 `json:\"lastLastMonthMemoryCost,omitempty\"`\n\tLastLastMonthStorageCost float64 `json:\"lastLastMonthStorageCost,omitempty\"`\n\tLastLastMonthCost        float64 `json:\"lastLastMonthCost,omitempty\"`\n}\n\n// CreateOrUpdateGroup updates group if it is already present in dgraph else it creates one\nfunc CreateOrUpdateGroup(group *groups_v1.Group, podsCount int) (*api.Assigned, error) {\n\txid := groupXIDPrefix + group.Name\n\tuid := dgraph.GetUID(xid, IsGroup)\n\n\thoursRemainingInCurrentMonth := utils.GetHoursRemainingInCurrentMonth()\n\tgrp := Group{\n\t\tID:                   dgraph.ID{Xid: xid},\n\t\tIsGroup:              true,\n\t\tName:                 group.Name,\n\t\tPodsCount:            podsCount,\n\t\tMtdCPU:               group.Spec.MTDMetrics.CPURequest,\n\t\tMtdMemory:            group.Spec.MTDMetrics.MemoryRequest,\n\t\tMtdStorage:           group.Spec.MTDMetrics.StorageClaim,\n\t\tCPU:                  group.Spec.PITMetrics.CPURequest,\n\t\tMemory:               group.Spec.PITMetrics.MemoryRequest,\n\t\tStorage:              group.Spec.PITMetrics.StorageClaim,\n\t\tMtdCPUCost:           group.Spec.MTDCost.CPUCost,\n\t\tMtdMemoryCost:        group.Spec.MTDCost.MemoryCost,\n\t\tMtdStorageCost:       group.Spec.MTDCost.StorageCost,\n\t\tMtdCost:              group.Spec.MTDCost.TotalCost,\n\t\tProjectedCPUCost:     group.Spec.MTDCost.CPUCost + group.Spec.PerHourCost.CPUCost*hoursRemainingInCurrentMonth,\n\t\tProjectedMemoryCost:  group.Spec.MTDCost.MemoryCost + group.Spec.PerHourCost.MemoryCost*hoursRemainingInCurrentMonth,\n\t\tProjectedStorageCost: group.Spec.MTDCost.StorageCost + group.Spec.PerHourCost.StorageCost*hoursRemainingInCurrentMonth,\n\t\tProjectedCost:        group.Spec.MTDCost.TotalCost + group.Spec.PerHourCost.TotalCost*hoursRemainingInCurrentMonth,\n\t\tLastMonthCPUCost:           group.Spec.LastMonthCost.CPUCost,\n\t\tLastMonthMemoryCost:        group.Spec.LastMonthCost.MemoryCost,\n\t\tLastMonthStorageCost:       group.Spec.LastMonthCost.StorageCost,\n\t\tLastMonthCost:              group.Spec.LastMonthCost.TotalCost,\n\t\tLastLastMonthCPUCost:           group.Spec.LastLastMonthCost.CPUCost,\n\t\tLastLastMonthMemoryCost:        group.Spec.LastLastMonthCost.MemoryCost,\n\t\tLastLastMonthStorageCost:       group.Spec.LastLastMonthCost.StorageCost,\n\t\tLastLastMonthCost:              group.Spec.LastLastMonthCost.TotalCost,\n\t}\n\tif uid != \"\" {\n\t\tgrp.ID = dgraph.ID{Xid: xid, UID: uid}\n\t}\n\treturn dgraph.MutateNode(grp, dgraph.CREATE)\n}\n\n// DeleteGroup deletes group from dgraph\nfunc DeleteGroup(name string) {\n\txid := groupXIDPrefix + name\n\tuid := dgraph.GetUID(xid, IsGroup)\n\n\tif uid != \"\" {\n\t\tgrp := Group{ID: dgraph.ID{UID: uid}}\n\t\t_, err := dgraph.MutateNode(grp, dgraph.DELETE)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"error while deleting group: %v, err: %v\", name, err)\n\t\t}\n\t\treturn\n\t}\n\tlogrus.Infof(\"Group: %s not yet persisted\", name)\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/job.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\tbatch_v1 \"k8s.io/api/batch/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsJob = \"isJob\"\n)\n\n// Job schema in dgraph\ntype Job struct {\n\tdgraph.ID\n\tIsJob     bool       `json:\"isJob,omitempty\"`\n\tName      string     `json:\"name,omitempty\"`\n\tStartTime string     `json:\"startTime,omitempty\"`\n\tEndTime   string     `json:\"endTime,omitempty\"`\n\tNamespace *Namespace `json:\"namespace,omitempty\"`\n\tPods      []*Pod     `json:\"pod,omitempty\"`\n\tType      string     `json:\"type,omitempty\"`\n}\n\nfunc createJobObject(job batch_v1.Job) Job {\n\tnewJob := Job{\n\t\tName:      \"job-\" + job.Name,\n\t\tIsJob:     true,\n\t\tType:      \"job\",\n\t\tID:        dgraph.ID{Xid: job.Namespace + \":\" + job.Name},\n\t\tStartTime: job.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(job.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewJob.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: job.Namespace}}\n\t}\n\tjobDeletionTimestamp := job.GetDeletionTimestamp()\n\tif !jobDeletionTimestamp.IsZero() {\n\t\tnewJob.EndTime = jobDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewJob.Xid += newJob.EndTime\n\t\tnewJob.Name += \"*\" + newJob.EndTime\n\t}\n\treturn newJob\n}\n\n// StoreJob create a new daemonset in the Dgraph and updates if already present.\nfunc StoreJob(job batch_v1.Job) (string, error) {\n\txid := job.Namespace + \":\" + job.Name\n\tuid := dgraph.GetUID(xid, IsJob)\n\n\tnewJob := createJobObject(job)\n\tif uid != \"\" {\n\t\tnewJob.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newJob, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// CreateOrGetJobByID returns the uid of namespace if exists,\n// otherwise creates the job and returns uid.\nfunc CreateOrGetJobByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsJob)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := Job{\n\t\tID:    dgraph.ID{Xid: xid},\n\t\tName:  xid,\n\t\tIsJob: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/label.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIslabel = \"isLabel\"\n)\n\n// Label structure for Key:Value\ntype Label struct {\n\tdgraph.ID\n\tIsLabel bool   `json:\"isLabel,omitempty\"`\n\tKey     string `json:\"key,omitempty\"`\n\tValue   string `json:\"value,omitempty\"`\n}\n\n// GetLabel if label is not in dgraph it creates and returns Label object\nfunc GetLabel(key, value string) *Label {\n\txid := getXIDOfLabel(key, value)\n\tuid := CreateOrGetLabelByID(key, value)\n\treturn &Label{\n\t\tID: dgraph.ID{Xid: xid, UID: uid},\n\t}\n}\n\n// CreateOrGetLabelByID if label is not in dgraph it creates and returns uid of label\nfunc CreateOrGetLabelByID(key, value string) string {\n\txid := getXIDOfLabel(key, value)\n\tuid := dgraph.GetUID(xid, Islabel)\n\tif uid == \"\" {\n\t\t// create new label and get its uid\n\t\tuid = createLabelObject(key, value)\n\t}\n\treturn uid\n}\n\nfunc getXIDOfLabel(key, value string) string {\n\treturn \"label-\" + key + \"-\" + value\n}\n\nfunc createLabelObject(key, value string) string {\n\txid := getXIDOfLabel(key, value)\n\tnewLabel := Label{\n\t\tID:      dgraph.ID{Xid: xid},\n\t\tIsLabel: true,\n\t\tKey:     key,\n\t\tValue:   value,\n\t}\n\tassigned, err := dgraph.MutateNode(newLabel, dgraph.CREATE)\n\tif err != nil {\n\t\tlogrus.Fatal(err)\n\t\treturn \"\"\n\t}\n\tlogrus.Debugf(\"created label in dgraph key: (%v), value: (%v)\", newLabel.Key, newLabel.Value)\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/namespace.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\n\tapi_v1 \"k8s.io/api/core/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsNamespace = \"isNamespace\"\n)\n\n// Namespace schema in dgraph\ntype Namespace struct {\n\tdgraph.ID\n\tIsNamespace bool   `json:\"isNamespace,omitempty\"`\n\tName        string `json:\"name,omitempty\"`\n\tStartTime   string `json:\"startTime,omitempty\"`\n\tEndTime     string `json:\"endTime,omitempty\"`\n\tType        string `json:\"type,omitempty\"`\n}\n\nfunc newNamespace(namespace api_v1.Namespace) Namespace {\n\tns := Namespace{\n\t\tID:          dgraph.ID{Xid: namespace.Name},\n\t\tName:        \"namespace-\" + namespace.Name,\n\t\tIsNamespace: true,\n\t\tType:        \"namespace\",\n\t\tStartTime:   namespace.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnsDeletionTimestamp := namespace.GetDeletionTimestamp()\n\tif !nsDeletionTimestamp.IsZero() {\n\t\tns.EndTime = nsDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tns.Xid += ns.EndTime\n\t\tns.Name += \"*\" + ns.EndTime\n\t}\n\treturn ns\n}\n\n// CreateOrGetNamespaceByID returns the uid of namespace if exists,\n// otherwise creates the namespace and returns uid.\nfunc CreateOrGetNamespaceByID(xid string) string {\n\tif xid == \"\" {\n\t\tlog.Error(\"Namespace is empty\")\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsNamespace)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\tns := Namespace{\n\t\tID:          dgraph.ID{Xid: xid},\n\t\tName:        xid,\n\t\tIsNamespace: true,\n\t}\n\tassigned, err := dgraph.MutateNode(ns, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Error(err)\n\t\treturn \"\"\n\t}\n\tlog.Infof(\"Namespace with xid: (%s) persisted\", xid)\n\treturn assigned.Uids[\"blank-0\"]\n}\n\n// StoreNamespace create a new namespace in the Dgraph  if it is not present.\nfunc StoreNamespace(namespace api_v1.Namespace) (string, error) {\n\txid := namespace.Name\n\tuid := dgraph.GetUID(xid, IsNamespace)\n\n\tns := newNamespace(namespace)\n\tif uid != \"\" {\n\t\tns.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(ns, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif uid == \"\" {\n\t\tlog.Infof(\"Namespace with xid: (%s) persisted\", xid)\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/node.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\t\"fmt\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsNode               = \"isNode\"\n\tDefaultNodeInstance  = \"purser-default\"\n\tDefaultNodeOS        = \"purser-default\"\n\tInstanceTypeLabelKey = \"beta.kubernetes.io/instance-type\"\n\tOSLabelKey           = \"beta.kubernetes.io/os\"\n)\n\n// Node schema in dgraph\ntype Node struct {\n\tdgraph.ID\n\tIsNode         bool    `json:\"isNode,omitempty\"`\n\tName           string  `json:\"name,omitempty\"`\n\tStartTime      string  `json:\"startTime,omitempty\"`\n\tEndTime        string  `json:\"endTime,omitempty\"`\n\tPods           []*Pod  `json:\"pods,omitempty\"`\n\tCPUCapacity    float64 `json:\"cpuCapacity,omitempty\"`\n\tMemoryCapacity float64 `json:\"memoryCapacity,omitempty\"`\n\tType           string  `json:\"type,omitempty\"`\n\tInstanceType   string  `json:\"instanceType,omitempty\"`\n\tOS             string  `json:\"os,omitempty\"`\n\tCPUPrice       float64 `json:\"cpuPrice,omitempty\"`\n\tMemoryPrice    float64 `json:\"memoryPrice,omitempty\"`\n}\n\nfunc createNodeObject(node api_v1.Node) Node {\n\tnewNode := Node{\n\t\tName:           \"node-\" + node.Name,\n\t\tIsNode:         true,\n\t\tType:           \"node\",\n\t\tID:             dgraph.ID{Xid: node.Name},\n\t\tStartTime:      node.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t\tCPUCapacity:    utils.ConvertToFloat64CPU(node.Status.Capacity.Cpu()),\n\t\tMemoryCapacity: utils.ConvertToFloat64GB(node.Status.Capacity.Memory()),\n\t}\n\n\tinstanceType, os := getInstanceTypeAndOS(node)\n\tnewNode.InstanceType = instanceType\n\tnewNode.OS = os\n\tlog.Debugf(\"node: %s, instanceType: %s, os: %s\", node.Name, newNode.InstanceType, newNode.OS)\n\n\tnodeDeletionTimestamp := node.GetDeletionTimestamp()\n\tif !nodeDeletionTimestamp.IsZero() {\n\t\tnewNode.EndTime = nodeDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewNode.Xid += newNode.EndTime\n\t\tnewNode.Name += \"*\" + newNode.EndTime\n\t}\n\treturn newNode\n}\n\n// createOrGetNodeByID create and returns the node if not present, otherwise simply returns node.\nfunc createOrGetNodeByID(xid string) (string, error) {\n\tif xid == \"\" {\n\t\treturn \"\", fmt.Errorf(\"node xid is empty\")\n\t}\n\tuid := dgraph.GetUID(xid, IsNode)\n\tif uid != \"\" {\n\t\treturn uid, nil\n\t}\n\tnewNode := Node{\n\t\tName:   xid,\n\t\tIsNode: true,\n\t\tID:     dgraph.ID{Xid: xid},\n\t}\n\tassigned, err := dgraph.MutateNode(newNode, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tlog.Infof(\"Node with xid: (%s) persisted\", xid)\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// StoreNode create a new node in the Dgraph  if it is not present.\nfunc StoreNode(node api_v1.Node) (string, error) {\n\txid := node.Name\n\tuid := dgraph.GetUID(xid, IsNode)\n\n\tnewNode := createNodeObject(node)\n\tif uid != \"\" {\n\t\tnewNode.UID = uid\n\t}\n\n\tnewNode.CPUPrice, newNode.MemoryPrice = getPricePerUnitResourceFromNodePrice(newNode)\n\tassigned, err := dgraph.MutateNode(newNode, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif uid == \"\" {\n\t\tlog.Infof(\"Node with xid: (%s) persisted\", xid)\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// getInstanceTypeAndOS returns instance and os of a node\nfunc getInstanceTypeAndOS(node api_v1.Node) (string, string) {\n\tnodeLabels := node.GetLabels()\n\tinstanceType := DefaultNodeInstance\n\tos := DefaultNodeOS\n\n\tif value, isPresent := nodeLabels[InstanceTypeLabelKey]; isPresent {\n\t\tinstanceType = value\n\t}\n\tif value, isPresent := nodeLabels[OSLabelKey]; isPresent {\n\t\tos = value\n\t}\n\n\treturn instanceType, os\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/pod.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/dgraph-io/dgo/protos/api\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\n\tapi_v1 \"k8s.io/api/core/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsPod = \"isPod\"\n)\n\n// Pod schema in dgraph\ntype Pod struct {\n\tdgraph.ID\n\tIsPod          bool                     `json:\"isPod,omitempty\"`\n\tName           string                   `json:\"name,omitempty\"`\n\tStartTime      string                   `json:\"startTime,omitempty\"`\n\tEndTime        string                   `json:\"endTime,omitempty\"`\n\tContainers     []*Container             `json:\"containers,omitempty\"`\n\tPods           []*Pod                   `json:\"pod,omitempty\"`\n\tCount          float64                  `json:\"pod|count,omitempty\"`\n\tNode           *Node                    `json:\"node,omitempty\"`\n\tNamespace      *Namespace               `json:\"namespace,omitempty\"`\n\tDeployment     *Deployment              `json:\"deployment,omitempty\"`\n\tReplicaset     *Replicaset              `json:\"replicaset,omitempty\"`\n\tStatefulset    *Statefulset             `json:\"statefulset,omitempty\"`\n\tDaemonset      *Daemonset               `json:\"daemonset,omitempty\"`\n\tJob            *Job                     `json:\"job,omitempty\"`\n\tPvcs           []*PersistentVolumeClaim `json:\"pvc,omitempty\"`\n\tCPURequest     float64                  `json:\"cpuRequest,omitempty\"`\n\tCPULimit       float64                  `json:\"cpuLimit,omitempty\"`\n\tMemoryRequest  float64                  `json:\"memoryRequest,omitempty\"`\n\tMemoryLimit    float64                  `json:\"memoryLimit,omitempty\"`\n\tStorageRequest float64                  `json:\"storageRequest,omitempty\"`\n\tType           string                   `json:\"type,omitempty\"`\n\tCid            []Service                `json:\"cid,omitempty\"`\n\tLabels         []*Label                 `json:\"label,omitempty\"`\n\tCPUPrice       float64                  `json:\"cpuPrice,omitempty\"`\n\tMemoryPrice    float64                  `json:\"memoryPrice,omitempty\"`\n}\n\n// Metrics ...\ntype Metrics struct {\n\tCPURequest    float64\n\tCPULimit      float64\n\tMemoryRequest float64\n\tMemoryLimit   float64\n}\n\n// newPod creates a new node for the pod in the Dgraph\nfunc newPod(k8sPod api_v1.Pod) (*api.Assigned, error) {\n\tpod := Pod{\n\t\tName:      \"pod-\" + k8sPod.Name,\n\t\tIsPod:     true,\n\t\tType:      \"pod\",\n\t\tID:        dgraph.ID{Xid: k8sPod.Namespace + \":\" + k8sPod.Name},\n\t\tStartTime: k8sPod.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnodeUID, err := createOrGetNodeByID(k8sPod.Spec.NodeName)\n\tif err == nil {\n\t\tpod.Node = &Node{ID: dgraph.ID{UID: nodeUID, Xid: k8sPod.Spec.NodeName}}\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(k8sPod.Namespace)\n\tif namespaceUID != \"\" {\n\t\tpod.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: k8sPod.Namespace}}\n\t}\n\tpod.Pvcs, pod.StorageRequest = getPodVolumes(k8sPod)\n\tsetPodOwners(&pod, k8sPod)\n\treturn dgraph.MutateNode(pod, dgraph.CREATE)\n}\n\n// StorePod updates the pod details and create it a new node if not exists.\n// It also populates Containers of a pod.\nfunc StorePod(k8sPod api_v1.Pod) error {\n\tif k8sPod.Namespace == \"\" || k8sPod.Name == \"\" {\n\t\treturn fmt.Errorf(\"pod name/namespace is empty, name: %s, namesapce: %s\", k8sPod.Name, k8sPod.Namespace)\n\t}\n\n\txid := k8sPod.Namespace + \":\" + k8sPod.Name\n\tuid := dgraph.GetUID(xid, IsPod)\n\n\tvar pod Pod\n\tif uid == \"\" {\n\t\tassigned, err := newPod(k8sPod)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.Infof(\"Pod with xid: (%s) persisted in dgraph\", xid)\n\t\tuid = assigned.Uids[\"blank-0\"]\n\t}\n\n\tpodDeletedTimestamp := k8sPod.GetDeletionTimestamp()\n\tif !podDeletedTimestamp.IsZero() {\n\t\tendTime := podDeletedTimestamp.Time.Format(time.RFC3339)\n\t\tpod = Pod{\n\t\t\tID:      dgraph.ID{Xid: xid + endTime, UID: uid},\n\t\t\tEndTime: endTime,\n\t\t\tName:    \"pod-\" + k8sPod.Name + \"*\" + endTime,\n\t\t}\n\t\tpodData := RetrievePodWithContainers(xid)\n\t\tdeleteContainersInTerminatedPod(podData.Containers, podDeletedTimestamp.Time)\n\t} else {\n\t\tnamespaceUID := CreateOrGetNamespaceByID(k8sPod.Namespace)\n\t\tcontainers, metrics := StoreAndRetrieveContainersAndMetrics(k8sPod, uid, namespaceUID)\n\t\tpod = Pod{\n\t\t\tID:            dgraph.ID{Xid: xid, UID: uid},\n\t\t\tName:          \"pod-\" + k8sPod.Name,\n\t\t\tContainers:    containers,\n\t\t\tCPURequest:    metrics.CPURequest,\n\t\t\tCPULimit:      metrics.CPULimit,\n\t\t\tMemoryRequest: metrics.MemoryRequest,\n\t\t\tMemoryLimit:   metrics.MemoryLimit,\n\t\t}\n\t\tpopulatePodLabels(&pod, k8sPod.Labels)\n\t}\n\n\t// store/update CPUPrice, MemoryPrice\n\tpod.CPUPrice, pod.MemoryPrice = getPerUnitResourcePriceForNode(\"node-\" + k8sPod.Spec.NodeName)\n\n\t_, err := dgraph.MutateNode(pod, dgraph.UPDATE)\n\treturn err\n}\n\n// StorePodsInteraction store the pod interactions in Dgraph\nfunc StorePodsInteraction(sourcePodXID string, destinationPodsXIDs []string, counts []float64) error {\n\tuid := dgraph.GetUID(sourcePodXID, IsPod)\n\tif uid == \"\" {\n\t\tlog.Println(\"Source Pod \" + sourcePodXID + \" is not persisted yet.\")\n\t\treturn fmt.Errorf(\"source pod: %s is not persisted yet\", sourcePodXID)\n\t}\n\n\tpods := retrievePodsWithCountAsEdgeWeightFromPodsXIDs(destinationPodsXIDs, counts)\n\tsource := Pod{\n\t\tID:   dgraph.ID{UID: uid, Xid: sourcePodXID},\n\t\tPods: pods,\n\t}\n\t_, err := dgraph.MutateNode(source, dgraph.UPDATE)\n\treturn err\n}\n\nfunc retrievePodsFromPodsXIDs(podsXIDs []string) []*Pod {\n\tpods := []*Pod{}\n\tfor _, podXID := range podsXIDs {\n\t\tpodUID := dgraph.GetUID(podXID, IsPod)\n\t\tif podUID == \"\" {\n\t\t\tlog.Debugf(\"Pod uid is empty for pod xid: %s\", podXID)\n\t\t\tcontinue\n\t\t}\n\t\tpod := &Pod{\n\t\t\tID: dgraph.ID{UID: podUID, Xid: podXID},\n\t\t}\n\t\tpods = append(pods, pod)\n\t}\n\treturn pods\n}\n\nfunc retrievePodsWithCountAsEdgeWeightFromPodsXIDs(podsXIDs []string, counts []float64) []*Pod {\n\tpods := []*Pod{}\n\tfor index, podXID := range podsXIDs {\n\t\tpodUID := dgraph.GetUID(podXID, IsPod)\n\t\tif podUID == \"\" {\n\t\t\tlog.Printf(\"Destination pod: %s is not persisted yet\", podXID)\n\t\t\tcontinue\n\t\t}\n\n\t\tpod := &Pod{\n\t\t\tID:    dgraph.ID{UID: podUID, Xid: podXID},\n\t\t\tCount: counts[index],\n\t\t}\n\t\tpods = append(pods, pod)\n\t}\n\treturn pods\n}\n\nfunc setPodOwners(pod *Pod, k8sPod api_v1.Pod) {\n\towners := k8sPod.GetObjectMeta().GetOwnerReferences()\n\tfor _, owner := range owners {\n\t\townerXID := k8sPod.Namespace + \":\" + owner.Name\n\t\tswitch owner.Kind {\n\t\tcase \"Deployment\":\n\t\t\tupdateDeploymentAsPodOwner(pod, ownerXID)\n\t\tcase \"ReplicaSet\":\n\t\t\tupdateReplicasetAsPodOwner(pod, ownerXID)\n\t\tcase \"StatefulSet\":\n\t\t\tupdateStatefulsetAsPodOwner(pod, ownerXID)\n\t\tcase \"Job\":\n\t\t\tupdateJobAsPodOwner(pod, ownerXID)\n\t\tcase \"DaemonSet\":\n\t\t\tupdateDaemonsetAsPodOwner(pod, ownerXID)\n\t\tdefault:\n\t\t\tlog.Error(\"Unknown owner type \" + owner.Kind + \" for pod.\")\n\t\t}\n\t}\n}\n\nfunc updateDeploymentAsPodOwner(pod *Pod, ownerXID string) {\n\tdeploymentUID := CreateOrGetDeploymentByID(ownerXID)\n\tif deploymentUID != \"\" {\n\t\tpod.Deployment = &Deployment{ID: dgraph.ID{UID: deploymentUID, Xid: ownerXID}}\n\t}\n}\n\nfunc updateReplicasetAsPodOwner(pod *Pod, ownerXID string) {\n\treplicasetUID := CreateOrGetReplicasetByID(ownerXID)\n\tif replicasetUID != \"\" {\n\t\tpod.Replicaset = &Replicaset{ID: dgraph.ID{UID: replicasetUID, Xid: ownerXID}}\n\t}\n}\n\nfunc updateStatefulsetAsPodOwner(pod *Pod, ownerXID string) {\n\tstatefulsetUID := CreateOrGetStatefulsetByID(ownerXID)\n\tif statefulsetUID != \"\" {\n\t\tpod.Statefulset = &Statefulset{ID: dgraph.ID{UID: statefulsetUID, Xid: ownerXID}}\n\t}\n}\n\nfunc updateJobAsPodOwner(pod *Pod, ownerXID string) {\n\tjobUID := CreateOrGetJobByID(ownerXID)\n\tif jobUID != \"\" {\n\t\tpod.Job = &Job{ID: dgraph.ID{UID: jobUID, Xid: ownerXID}}\n\t}\n}\n\nfunc updateDaemonsetAsPodOwner(pod *Pod, ownerXID string) {\n\tdaemonsetUID := CreateOrGetDaemonsetByID(ownerXID)\n\tif daemonsetUID != \"\" {\n\t\tpod.Daemonset = &Daemonset{ID: dgraph.ID{UID: daemonsetUID, Xid: ownerXID}}\n\t}\n}\n\nfunc getPodVolumes(k8sPod api_v1.Pod) ([]*PersistentVolumeClaim, float64) {\n\tpodVolumes := []*PersistentVolumeClaim{}\n\tstorage := 0.0\n\tfor j := 0; j < len(k8sPod.Spec.Volumes); j++ {\n\t\tvol := k8sPod.Spec.Volumes[j]\n\t\tif vol.PersistentVolumeClaim != nil {\n\t\t\tpvcXID := k8sPod.Namespace + \":\" + vol.PersistentVolumeClaim.ClaimName\n\t\t\tpvcUID := CreateOrGetPersistentVolumeClaimByID(pvcXID)\n\t\t\tif pvcUID != \"\" {\n\t\t\t\tpodVolumes = append(podVolumes, &PersistentVolumeClaim{ID: dgraph.ID{UID: pvcUID, Xid: pvcXID}})\n\t\t\t\tpvc, err := getPVCFromUID(pvcUID)\n\t\t\t\tif err == nil {\n\t\t\t\t\tstorage += pvc.StorageCapacity\n\t\t\t\t} else {\n\t\t\t\t\tlog.Errorf(\"error while getting pvc from uid: (%v), error: (%v)\", pvcUID, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn podVolumes, storage\n}\n\nfunc populatePodLabels(pod *Pod, podLabels map[string]string) {\n\tlog.Debugf(\"k8s pod: (%v), labels: (%v)\", pod.Name, podLabels)\n\tvar labels []*Label\n\tfor key, value := range podLabels {\n\t\tlabels = append(labels, GetLabel(key, value))\n\t}\n\tpod.Labels = labels\n}\n\n// RetrievePodWithContainers given a name of pod it retrieves its containers\nfunc RetrievePodWithContainers(xid string) Pod {\n\tquery := `query {\n\t\tpods(func: has(isPod)) @filter(eq(xid, \"` + xid + `\")) {\n\t\t\tname\n\t\t\tcontainers: ~pod @filter(has(isContainer)) {\n\t\t\t\tuid\n\t\t\t}\n\t\t}\n\t}`\n\ttype root struct {\n\t\tPods []Pod `json:\"pods\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(query, &newRoot)\n\tif err != nil || len(newRoot.Pods) < 1 {\n\t\tlog.Errorf(\"unable to retrieve pod with containers: %v\", err)\n\t\treturn Pod{}\n\t}\n\treturn newRoot.Pods[0]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/pod_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n)\n\n// TestStorePodsInteraction ...\nfunc TestStorePodsInteraction(t *testing.T) {\n\tfmt.Println(\"Hello World\")\n\terr := dgraph.Open(\"127.0.0.1:9080\")\n\tif err != nil {\n\t\tfmt.Println(\"Error while opening connection to Dgraph \", err)\n\t}\n\n\terr = dgraph.CreateSchema()\n\tif err != nil {\n\t\tfmt.Println(\"Error while creating schema \", err)\n\t}\n\n\tsourcePod := \"weave:weave-scope-app-6d6b76b846-z92wk\"\n\tdestinationPods := []string{\"fiaasco:ccs-billing-deployment-1-1-92-75dc8749f4-gld6q\", \"weave:weave-scope-agent-lbfpj\"}\n\tinteractionCounts := []float64{2.0}\n\n\terr = StorePodsInteraction(sourcePod, destinationPods, interactionCounts)\n\tif err != nil {\n\t\tfmt.Println(\"Error while building interation graph \", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/process.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Sirupsen/logrus\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/dgraph-io/dgo/protos/api\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsProc = \"isProc\"\n)\n\n// Proc schema in dgraph\ntype Proc struct {\n\tdgraph.ID\n\tIsProc    bool       `json:\"isProc,omitempty\"`\n\tName      string     `json:\"name,omitempty\"`\n\tInteracts []*Pod     `json:\"interacts,omitempty\"`\n\tContainer Container  `json:\"container,omitempty\"`\n\tStartTime string     `json:\"startTime,omitempty\"`\n\tEndTime   string     `json:\"endTime,omitempty\"`\n\tNamespace *Namespace `json:\"namespace,omitempty\"`\n\tType      string     `json:\"type,omitempty\"`\n}\n\nfunc newProc(procXID, procName, containerUID, containerXID string, creationTimeStamp time.Time) (*api.Assigned, error) {\n\tnewProc := Proc{\n\t\tID:        dgraph.ID{Xid: procXID},\n\t\tIsProc:    true,\n\t\tType:      \"process\",\n\t\tName:      \"process-\" + procName,\n\t\tContainer: Container{ID: dgraph.ID{UID: containerUID, Xid: containerXID}},\n\t\tStartTime: creationTimeStamp.Format(time.RFC3339),\n\t}\n\treturn dgraph.MutateNode(newProc, dgraph.CREATE)\n}\n\n// StoreProcess ...\nfunc StoreProcess(procXID, containerXID string, podsXIDs []string, creationTimeStamp time.Time) error {\n\t// fetch the 4th field from ns : podName : containerName : procID : procName\n\tprocName := strings.Join(strings.Split(procXID, \":\")[4:], \"-\")\n\tcontainerUID := dgraph.GetUID(containerXID, IsContainer)\n\tif containerUID == \"\" {\n\t\treturn fmt.Errorf(\"container not persisted yet\")\n\t}\n\n\tprocUID := dgraph.GetUID(procXID, IsProc)\n\tif procUID == \"\" {\n\t\tassigned, err := newProc(procXID, procName, containerUID, containerXID, creationTimeStamp)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"unable to create proc: %s\", procXID)\n\t\t\treturn err\n\t\t}\n\t\tlog.Debugf(\"Process with xid: (%s) persisted in dgraph\", procXID)\n\t\tprocUID = assigned.Uids[\"blank-0\"]\n\t}\n\n\tpods := retrievePodsFromPodsXIDs(podsXIDs)\n\tupdatedProc := Proc{\n\t\tID:        dgraph.ID{UID: procUID, Xid: procXID},\n\t\tInteracts: pods,\n\t}\n\t_, err := dgraph.MutateNode(updatedProc, dgraph.UPDATE)\n\treturn err\n}\n\nfunc deleteProcessesInTerminatedContainers(containers []*Container) {\n\tprocs := []Proc{}\n\tfor _, container := range containers {\n\t\tfor _, proc := range container.Procs {\n\t\t\tupdatedProc := Proc{\n\t\t\t\tID:      dgraph.ID{UID: proc.ID.UID, Xid: proc.ID.Xid},\n\t\t\t\tEndTime: container.EndTime,\n\t\t\t}\n\t\t\tprocs = append(procs, updatedProc)\n\t\t}\n\t}\n\t_, err := dgraph.MutateNode(procs, dgraph.UPDATE)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n}\n\nfunc retrieveProcessesFromProcessesXIDs(procsXIDs []string) []*Proc {\n\tprocs := []*Proc{}\n\tfor _, procXID := range procsXIDs {\n\t\tprocUID := dgraph.GetUID(procXID, IsProc)\n\t\tif procUID != \"\" {\n\t\t\tprocs = append(procs, &Proc{ID: dgraph.ID{UID: procUID, Xid: procXID}})\n\t\t}\n\t}\n\treturn procs\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/pv.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"k8s.io/client-go/kubernetes\"\n\n\t\"log\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsPersistentVolume = \"isPersistentVolume\"\n)\n\n// PersistentVolume schema in dgraph\ntype PersistentVolume struct {\n\tdgraph.ID\n\tIsPersistentVolume bool    `json:\"isPersistentVolume,omitempty\"`\n\tName               string  `json:\"name,omitempty\"`\n\tStartTime          string  `json:\"startTime,omitempty\"`\n\tEndTime            string  `json:\"endTime,omitempty\"`\n\tType               string  `json:\"type,omitempty\"`\n\tStorageCapacity    float64 `json:\"storageCapacity,omitempty\"`\n\tStorageType        string  `json:\"storageType,omitempty\"`\n}\n\nfunc createPersistentVolumeObject(pv api_v1.PersistentVolume, client *kubernetes.Clientset) PersistentVolume {\n\tnewPv := PersistentVolume{\n\t\tName:               \"pv-\" + pv.Name,\n\t\tIsPersistentVolume: true,\n\t\tType:               \"pv\",\n\t\tID:                 dgraph.ID{Xid: pv.Name},\n\t\tStartTime:          pv.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tcapacity := pv.Spec.Capacity[\"storage\"]\n\tnewPv.StorageCapacity = utils.ConvertToFloat64GB(&capacity)\n\tnewPv.StorageType = utils.GetFinalStorageTypeOfPV(pv, client)\n\tlogrus.Debugf(\"PV: %s, storageType: %s\", newPv.Name, newPv.StorageType)\n\n\tdeletionTimestamp := pv.GetDeletionTimestamp()\n\tif !deletionTimestamp.IsZero() {\n\t\tnewPv.EndTime = deletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewPv.Xid += newPv.EndTime\n\t\tnewPv.Name += \"*\" + newPv.EndTime\n\t}\n\treturn newPv\n}\n\n// StorePersistentVolume create a new persistent volume in the Dgraph and updates if already present.\nfunc StorePersistentVolume(pv api_v1.PersistentVolume, client *kubernetes.Clientset) (string, error) {\n\txid := pv.Name\n\tuid := dgraph.GetUID(xid, IsPersistentVolume)\n\n\tnewPv := createPersistentVolumeObject(pv, client)\n\tif uid != \"\" {\n\t\tnewPv.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newPv, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// CreateOrGetPersistentVolumeByID returns the uid of persistent volume if exists,\n// otherwise creates the persistent volume and returns uid.\nfunc CreateOrGetPersistentVolumeByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsPersistentVolume)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := PersistentVolume{\n\t\tID:                 dgraph.ID{Xid: xid},\n\t\tName:               xid,\n\t\tIsPersistentVolume: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/pvc.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\t\"log\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsPersistentVolumeClaim = \"isPersistentVolumeClaim\"\n)\n\n// PersistentVolumeClaim schema in dgraph\ntype PersistentVolumeClaim struct {\n\tdgraph.ID\n\tIsPersistentVolumeClaim bool              `json:\"isPersistentVolumeClaim,omitempty\"`\n\tName                    string            `json:\"name,omitempty\"`\n\tStartTime               string            `json:\"startTime,omitempty\"`\n\tEndTime                 string            `json:\"endTime,omitempty\"`\n\tNamespace               *Namespace        `json:\"namespace,omitempty\"`\n\tType                    string            `json:\"type,omitempty\"`\n\tStorageCapacity         float64           `json:\"storageCapacity,omitempty\"`\n\tPersistentVolume        *PersistentVolume `json:\"pv,omitempty\"`\n}\n\nfunc createPvcObject(pvc api_v1.PersistentVolumeClaim) PersistentVolumeClaim {\n\tnewPvc := PersistentVolumeClaim{\n\t\tName:                    \"pvc-\" + pvc.Name,\n\t\tIsPersistentVolumeClaim: true,\n\t\tType:                    \"pvc\",\n\t\tID:                      dgraph.ID{Xid: pvc.Namespace + \":\" + pvc.Name},\n\t\tStartTime:               pvc.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tcapacity := pvc.Status.Capacity[\"storage\"]\n\tnewPvc.StorageCapacity = utils.ConvertToFloat64GB(&capacity)\n\n\tvolume := pvc.Spec.VolumeName\n\tpvUID := CreateOrGetPersistentVolumeByID(volume)\n\tif volume != \"\" {\n\t\tnewPvc.PersistentVolume = &PersistentVolume{ID: dgraph.ID{UID: pvUID}}\n\t}\n\n\tnamespaceUID := CreateOrGetNamespaceByID(pvc.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewPvc.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: pvc.Namespace}}\n\t}\n\tdeletionTimestamp := pvc.GetDeletionTimestamp()\n\tif !deletionTimestamp.IsZero() {\n\t\tnewPvc.EndTime = deletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewPvc.Xid += newPvc.EndTime\n\t\tnewPvc.Name += \"*\" + newPvc.EndTime\n\t}\n\treturn newPvc\n}\n\n// StorePersistentVolumeClaim create a new pvc in the Dgraph and updates if already present.\nfunc StorePersistentVolumeClaim(pvc api_v1.PersistentVolumeClaim) (string, error) {\n\txid := pvc.Namespace + \":\" + pvc.Name\n\tuid := dgraph.GetUID(xid, IsPersistentVolumeClaim)\n\n\tnewPvc := createPvcObject(pvc)\n\tif uid != \"\" {\n\t\tnewPvc.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newPvc, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// CreateOrGetPersistentVolumeClaimByID returns the uid of pvc if exists,\n// otherwise creates the pvc and returns uid.\nfunc CreateOrGetPersistentVolumeClaimByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsPersistentVolumeClaim)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := PersistentVolumeClaim{\n\t\tID:                      dgraph.ID{Xid: xid},\n\t\tName:                    xid,\n\t\tIsPersistentVolumeClaim: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n\nfunc getPVCFromUID(uid string) (PersistentVolumeClaim, error) {\n\tq := `query {\n\t\tpvcs(func: uid(` + uid + `)) {\n\t\t\tname\n\t\t\ttype\n\t\t\tstorageCapacity\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tPvcs []PersistentVolumeClaim `json:\"pvcs\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn PersistentVolumeClaim{}, err\n\t}\n\treturn newRoot.Pvcs[0], nil\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/cluster.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n)\n\nvar executeQuery = dgraph.ExecuteQuery\nvar executeQueryRaw = dgraph.ExecuteQueryRaw\n\nvar allocatedAndCapacity *ParentWrapper\n\nfunc getClusterHierarchyQuery(view string) string {\n\tswitch view {\n\tcase Physical:\n\t\treturn getHierarchyQueryForPhysicalResource()\n\tcase Logical:\n\t\treturn getHierarchyQueryForLogicalResource()\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n// RetrieveClusterHierarchy returns all namespaces if view is logical and returns all nodes with disks if view is physical\nfunc RetrieveClusterHierarchy(view string) JSONDataWrapper {\n\tquery := getClusterHierarchyQuery(view)\n\n\tparentRoot := ParentWrapper{}\n\terr := executeQuery(query, &parentRoot)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to execute query for retrieving cluster hierarchy: (%v)\", err)\n\t\treturn JSONDataWrapper{}\n\t}\n\troot := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:     \"cluster\",\n\t\t\tType:     \"cluster\",\n\t\t\tChildren: parentRoot.Children,\n\t\t},\n\t}\n\tlogrus.Debugf(\"data: (%v)\", root.Data)\n\treturn root\n}\n\nfunc getClusterMetricsQuery(view string) string {\n\tswitch view {\n\tcase Physical:\n\t\treturn getMetricsQueryForPhysicalResources()\n\tcase Logical:\n\t\treturn getMetricsQueryForLogicalResources()\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n// RetrieveClusterMetrics returns all namespaces with metrics if view is logical and\n// returns all nodes and disks with metrics if view is physical\nfunc RetrieveClusterMetrics(view string) JSONDataWrapper {\n\tquery := getClusterMetricsQuery(view)\n\tparentRoot := ParentWrapper{}\n\terr := executeQuery(query, &parentRoot)\n\tcalculateAggregateMetrics(&parentRoot)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to execute query for retrieving cluster metrics: (%v)\", err)\n\t\treturn JSONDataWrapper{}\n\t}\n\troot := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:        \"cluster\",\n\t\t\tType:        \"cluster\",\n\t\t\tChildren:    parentRoot.Children,\n\t\t\tCPU:         parentRoot.CPU,\n\t\t\tMemory:      parentRoot.Memory,\n\t\t\tStorage:     parentRoot.Storage,\n\t\t\tCPUCost:     parentRoot.CPUCost,\n\t\t\tMemoryCost:  parentRoot.MemoryCost,\n\t\t\tStorageCost: parentRoot.StorageCost,\n\t\t},\n\t}\n\tlogrus.Debugf(\"data: (%v)\", root.Data)\n\treturn root\n}\n\nfunc calculateAggregateMetrics(objRoot *ParentWrapper) {\n\tfor _, obj := range objRoot.Children {\n\t\tobjRoot.CPU += obj.CPU\n\t\tobjRoot.Memory += obj.Memory\n\t\tobjRoot.Storage += obj.Storage\n\t\tobjRoot.CPUCost += obj.CPUCost\n\t\tobjRoot.MemoryCost += obj.MemoryCost\n\t\tobjRoot.StorageCost += obj.StorageCost\n\t}\n}\n\n// PopulateClusterAllocationAndCapacity ...\nfunc PopulateClusterAllocationAndCapacity(jsonData *JSONDataWrapper) {\n\tif allocatedAndCapacity == nil {\n\t\tComputeClusterAllocationAndCapacity()\n\t}\n\tpopulateCapacityData(*allocatedAndCapacity, jsonData)\n}\n\n// ComputeClusterAllocationAndCapacity returns allocated, capacity for cpu, memory and storage\nfunc ComputeClusterAllocationAndCapacity() {\n\tallocation := RetrieveClusterMetrics(Logical)\n\tcapacity := RetrieveClusterMetrics(Physical)\n\tallocatedAndCapacity = &ParentWrapper{\n\t\tCPUAllocated:     allocation.Data.CPU,\n\t\tMemoryAllocated:  allocation.Data.Memory,\n\t\tStorageAllocated: allocation.Data.Storage,\n\t\tCPUCapacity:      capacity.Data.CPU,\n\t\tMemoryCapacity:   capacity.Data.Memory,\n\t\tStorageCapacity:  capacity.Data.Storage,\n\t}\n}\n\n// PopulateNodeOrPVAllocationAndCapacity returns allocated, capacity for cpu, memory and storage\nfunc (r *Resource) PopulateNodeOrPVAllocationAndCapacity(jsonData *JSONDataWrapper) {\n\tq := r.getQueryForResourceMetrics()\n\tresourceData := getJSONDataFromQuery(q)\n\tpopulateCapacityData(resourceData.Data, jsonData)\n}\n\nfunc populateCapacityData(allocatedAndCapacityData ParentWrapper, jsonData *JSONDataWrapper) {\n\tjsonData.Data.CPUAllocated = allocatedAndCapacityData.CPUAllocated\n\tjsonData.Data.MemoryAllocated = allocatedAndCapacityData.MemoryAllocated\n\tjsonData.Data.StorageAllocated = allocatedAndCapacityData.StorageAllocated\n\tjsonData.Data.CPUCapacity = allocatedAndCapacityData.CPUCapacity\n\tjsonData.Data.MemoryCapacity = allocatedAndCapacityData.MemoryCapacity\n\tjsonData.Data.StorageCapacity = allocatedAndCapacityData.StorageCapacity\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/cluster_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc mockSecondsSinceMonthStart() {\n\tsecondsFromFirstOfCurrentMonth = func() string {\n\t\treturn testSecondsSinceMonthStart\n\t}\n}\n\nfunc removeMocks() {\n\tsecondsFromFirstOfCurrentMonth = getSecondsSinceMonthStart\n\texecuteQuery = dgraph.ExecuteQuery\n\texecuteQueryRaw = dgraph.ExecuteQueryRaw\n}\n\n// TestMain ...\nfunc TestMain(m *testing.M) {\n\tmockSecondsSinceMonthStart()\n\tcode := m.Run()\n\tremoveMocks()\n\tos.Exit(code)\n}\n\nfunc mockDgraphForClusterQueries(queryType string) {\n\texecuteQuery = func(query string, root interface{}) error {\n\t\tif query == \"\" {\n\t\t\treturn fmt.Errorf(\"unable to connect/retrieve data from dgraph\")\n\t\t}\n\n\t\tdummyParentWrapper, ok := root.(*ParentWrapper)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t}\n\t\tvar first, second Children\n\t\tif queryType == testHierarchy {\n\t\t\tfirst = Children{\n\t\t\t\tName: \"namespace-first\",\n\t\t\t\tType: NamespaceType,\n\t\t\t}\n\t\t\tsecond = Children{\n\t\t\t\tName: \"namespace-second\",\n\t\t\t\tType: NamespaceType,\n\t\t\t}\n\t\t} else if queryType == testMetrics {\n\t\t\tfirst = Children{\n\t\t\t\tName:        \"namespace-first\",\n\t\t\t\tType:        NamespaceType,\n\t\t\t\tCPU:         0.90,\n\t\t\t\tMemory:      3,\n\t\t\t\tStorage:     1,\n\t\t\t\tCPUCost:     0.09,\n\t\t\t\tMemoryCost:  0.31,\n\t\t\t\tStorageCost: 0.11,\n\t\t\t}\n\t\t\tsecond = Children{\n\t\t\t\tName:        \"namespace-second\",\n\t\t\t\tType:        NamespaceType,\n\t\t\t\tCPU:         0.30,\n\t\t\t\tMemory:      9,\n\t\t\t\tStorage:     2,\n\t\t\t\tCPUCost:     0.03,\n\t\t\t\tMemoryCost:  0.91,\n\t\t\t\tStorageCost: 0.21,\n\t\t\t}\n\t\t} else if queryType == testCapacity {\n\t\t\tparent := ParentWrapper{\n\t\t\t\tCPUAllocated:     0.2,\n\t\t\t\tCPUCapacity:      0.4,\n\t\t\t\tMemoryAllocated:  1.2,\n\t\t\t\tMemoryCapacity:   1.8,\n\t\t\t\tStorageAllocated: 10.5,\n\t\t\t\tStorageCapacity:  20.1,\n\t\t\t}\n\t\t\tdummyParentWrapper.Parent = []ParentWrapper{parent}\n\t\t}\n\t\tchildren := []Children{first, second}\n\t\tdummyParentWrapper.Children = children\n\t\treturn nil\n\t}\n}\n\n// TestRetrieveClusterHierarchyNoView ...\nfunc TestRetrieveClusterHierarchyNoView(t *testing.T) {\n\tmockDgraphForClusterQueries(testHierarchy)\n\tgot := RetrieveClusterHierarchy(\"\")\n\texpected := JSONDataWrapper{}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveClusterHierarchyLogicalView ...\nfunc TestRetrieveClusterHierarchyLogicalView(t *testing.T) {\n\tmockDgraphForClusterQueries(testHierarchy)\n\tgot := RetrieveClusterHierarchy(Logical)\n\tfirstNamespace := Children{\n\t\tName: \"namespace-first\",\n\t\tType: NamespaceType,\n\t}\n\tsecondNamespace := Children{\n\t\tName: \"namespace-second\",\n\t\tType: NamespaceType,\n\t}\n\thierarchyChildren := []Children{firstNamespace, secondNamespace}\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:     \"cluster\",\n\t\t\tType:     \"cluster\",\n\t\t\tChildren: hierarchyChildren,\n\t\t},\n\t}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveClusterHierarchyPhysicalView ...\nfunc TestRetrieveClusterHierarchyPhysicalView(t *testing.T) {\n\tmockDgraphForClusterQueries(testHierarchy)\n\tgot := RetrieveClusterHierarchy(Physical)\n\tfirstNamespace := Children{\n\t\tName: \"namespace-first\",\n\t\tType: NamespaceType,\n\t}\n\tsecondNamespace := Children{\n\t\tName: \"namespace-second\",\n\t\tType: NamespaceType,\n\t}\n\thierarchyChildren := []Children{firstNamespace, secondNamespace}\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:     \"cluster\",\n\t\t\tType:     \"cluster\",\n\t\t\tChildren: hierarchyChildren,\n\t\t},\n\t}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveClusterMetricsNoView ...\nfunc TestRetrieveClusterMetricsNoView(t *testing.T) {\n\tmockDgraphForClusterQueries(testMetrics)\n\tgot := RetrieveClusterMetrics(\"\")\n\texpected := JSONDataWrapper{}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveClusterMetricsLogicalView ...\nfunc TestRetrieveClusterMetricsLogicalView(t *testing.T) {\n\tmockDgraphForClusterQueries(testMetrics)\n\tgot := RetrieveClusterMetrics(Logical)\n\tfirstNamespaceWithMetrics := Children{\n\t\tName:        \"namespace-first\",\n\t\tType:        NamespaceType,\n\t\tCPU:         0.90,\n\t\tMemory:      3,\n\t\tStorage:     1,\n\t\tCPUCost:     0.09,\n\t\tMemoryCost:  0.31,\n\t\tStorageCost: 0.11,\n\t}\n\tsecondNamespaceWithMetrics := Children{\n\t\tName:        \"namespace-second\",\n\t\tType:        NamespaceType,\n\t\tCPU:         0.30,\n\t\tMemory:      9,\n\t\tStorage:     2,\n\t\tCPUCost:     0.03,\n\t\tMemoryCost:  0.91,\n\t\tStorageCost: 0.21,\n\t}\n\tmetricsChildren := []Children{firstNamespaceWithMetrics, secondNamespaceWithMetrics}\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:        \"cluster\",\n\t\t\tType:        \"cluster\",\n\t\t\tChildren:    metricsChildren,\n\t\t\tCPU:         1.2,\n\t\t\tMemory:      12,\n\t\t\tStorage:     3,\n\t\t\tCPUCost:     0.12,\n\t\t\tMemoryCost:  1.22,\n\t\t\tStorageCost: 0.32,\n\t\t},\n\t}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveClusterMetricsPhysicalView ...\nfunc TestRetrieveClusterMetricsPhysicalView(t *testing.T) {\n\tmockDgraphForClusterQueries(testMetrics)\n\tgot := RetrieveClusterMetrics(Physical)\n\tfirstNamespaceWithMetrics := Children{\n\t\tName:        \"namespace-first\",\n\t\tType:        NamespaceType,\n\t\tCPU:         0.90,\n\t\tMemory:      3,\n\t\tStorage:     1,\n\t\tCPUCost:     0.09,\n\t\tMemoryCost:  0.31,\n\t\tStorageCost: 0.11,\n\t}\n\tsecondNamespaceWithMetrics := Children{\n\t\tName:        \"namespace-second\",\n\t\tType:        NamespaceType,\n\t\tCPU:         0.30,\n\t\tMemory:      9,\n\t\tStorage:     2,\n\t\tCPUCost:     0.03,\n\t\tMemoryCost:  0.91,\n\t\tStorageCost: 0.21,\n\t}\n\tmetricsChildren := []Children{firstNamespaceWithMetrics, secondNamespaceWithMetrics}\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:        \"cluster\",\n\t\t\tType:        \"cluster\",\n\t\t\tChildren:    metricsChildren,\n\t\t\tCPU:         1.20,\n\t\t\tMemory:      12,\n\t\t\tStorage:     3,\n\t\t\tCPUCost:     0.12,\n\t\t\tMemoryCost:  1.22,\n\t\t\tStorageCost: 0.32,\n\t\t},\n\t}\n\tassert.Equal(t, expected, got)\n}\n\nfunc TestPopulateClusterAllocationAndCapacityNil(t *testing.T) {\n\tmockDgraphForClusterQueries(testMetrics)\n\told := allocatedAndCapacity\n\tallocatedAndCapacity = nil\n\tdefer resetAllocatedAndCapacity(old)\n\n\tdata := &JSONDataWrapper{}\n\tPopulateClusterAllocationAndCapacity(data)\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tCPUAllocated:     1.2,\n\t\t\tCPUCapacity:      1.2,\n\t\t\tMemoryAllocated:  12,\n\t\t\tMemoryCapacity:   12,\n\t\t\tStorageAllocated: 3,\n\t\t\tStorageCapacity:  3,\n\t\t},\n\t}\n\tassert.Equal(t, expected, *data)\n}\n\nfunc TestPopulateClusterAllocationAndCapacity(t *testing.T) {\n\told := allocatedAndCapacity\n\tallocatedAndCapacity = &ParentWrapper{\n\t\tCPUAllocated:     0.2,\n\t\tCPUCapacity:      0.4,\n\t\tMemoryAllocated:  1.2,\n\t\tMemoryCapacity:   1.8,\n\t\tStorageAllocated: 10.5,\n\t\tStorageCapacity:  20.1,\n\t}\n\tdefer resetAllocatedAndCapacity(old)\n\n\tdata := &JSONDataWrapper{}\n\tPopulateClusterAllocationAndCapacity(data)\n\texpected := getTestAllocatedCapacityData()\n\tassert.Equal(t, expected, *data)\n}\n\nfunc resetAllocatedAndCapacity(old *ParentWrapper) {\n\tallocatedAndCapacity = old\n}\n\nfunc TestPopulateNodeOrPVAllocationAndCapacity(t *testing.T) {\n\tmockDgraphForClusterQueries(testCapacity)\n\tdata := &JSONDataWrapper{}\n\tr := &Resource{\n\t\tCheck: NodeCheck,\n\t\tType:  NodeType,\n\t\tName:  testResourceName,\n\t}\n\tr.PopulateNodeOrPVAllocationAndCapacity(data)\n\texpected := getTestAllocatedCapacityData()\n\tassert.Equal(t, expected, *data)\n}\n\nfunc getTestAllocatedCapacityData() JSONDataWrapper {\n\treturn JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tCPUAllocated:     0.2,\n\t\t\tCPUCapacity:      0.4,\n\t\t\tMemoryAllocated:  1.2,\n\t\t\tMemoryCapacity:   1.8,\n\t\t\tStorageAllocated: 10.5,\n\t\t\tStorageCapacity:  20.1,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/constants_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nconst (\n\ttestSecondsSinceMonthStart = \"1.45\"\n\ttestPodUIDList             = \"0x3e283, 0x3e288\"\n\ttestPodName                = \"pod-purser-dgraph-0\"\n\ttestDaemonsetName          = \"daemonset-purser\"\n\ttestResourceName           = \"resource-purser\"\n\ttestPodUID                 = \"0x3e283\"\n\ttestPodXID                 = \"purser:pod-purser-dgraph-0\"\n\n\ttestHierarchy            = \"hierarchy\"\n\ttestMetrics              = \"metrics\"\n\ttestRetrieveAllGroups    = \"retrieveAllGroups\"\n\ttestRetrieveGroupMetrics = \"retrieveGroupMetrics\"\n\ttestRetrieveSubscribers  = \"retrieveSubscribers\"\n\ttestLabelFilterPods      = \"labelFilterPods\"\n\ttestAlivePods            = \"alivePods\"\n\ttestPodInteractions      = \"podInteractions\"\n\ttestPodPrices            = \"podPrices\"\n\ttestCapacity             = \"capacityAllocation\"\n\ttestWrongQuery           = \"wrongQuery\"\n\ttestCPUPrice             = 0.24\n\ttestMemoryPrice          = 0.1\n)\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/group.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\t\"github.com/Sirupsen/logrus\"\n)\n\n// GroupMetrics structure\ntype GroupMetrics struct {\n\tPITCpu                   float64\n\tPITMemory                float64\n\tPITStorage               float64\n\tPITCpuLimit              float64\n\tPITMemoryLimit           float64\n\tMTDCpu                   float64\n\tMTDMemory                float64\n\tMTDStorage               float64\n\tMTDCpuLimit              float64\n\tMTDMemoryLimit           float64\n\tCostCPU                  float64\n\tCostMemory               float64\n\tCostStorage              float64\n\tCostCPUPerHour           float64\n\tCostMemoryPerHour        float64\n\tCostStoragePerHour       float64\n\tLastMonthCPUCost         float64\n\tLastMonthMemoryCost      float64\n\tLastMonthStorageCost     float64\n\tLastLastMonthCPUCost     float64\n\tLastLastMonthMemoryCost  float64\n\tLastLastMonthStorageCost float64\n\tPodsCount                int\n}\n\ntype groupsRoot struct {\n\tGroups []models.Group `json:\"groups,omitempty\"`\n}\n\ntype groupJSONMetrics struct {\n\tJSONMetrics []map[string]float64 `json:\"group\"`\n}\n\n// RetrieveGroupsData returns list of models.Group objects in json format\n// error is not nil if any failure is encountered\nfunc RetrieveGroupsData() ([]models.Group, error) {\n\tquery := getQueryForAllGroupsData()\n\n\tnewRoot := groupsRoot{}\n\terr := executeQuery(query, &newRoot)\n\tif err != nil {\n\t\treturn []models.Group{}, err\n\t}\n\treturn newRoot.Groups, nil\n}\n\n// RetrieveGroupMetricsFromPodUIDs ...\nfunc RetrieveGroupMetricsFromPodUIDs(podsUIDs string) (GroupMetrics, error) {\n\tquery := getQueryForGroupMetrics(podsUIDs)\n\n\tnewRoot := groupJSONMetrics{}\n\terr := executeQuery(query, &newRoot)\n\tif err != nil {\n\t\treturn GroupMetrics{}, err\n\t}\n\treturn convertToGroupMetrics(newRoot.JSONMetrics), nil\n}\n\nfunc convertToGroupMetrics(jsonMetrics []map[string]float64) GroupMetrics {\n\tvar groupMetrics GroupMetrics\n\tfor _, data := range jsonMetrics {\n\t\tfor key, value := range data {\n\t\t\tpopulateMetric(&groupMetrics, key, value)\n\t\t\tbreak\n\t\t}\n\t}\n\tlogrus.Debugf(\"JSON metrics: (%v), Group metrics: (%v)\", jsonMetrics, groupMetrics)\n\treturn groupMetrics\n}\n\n// nolint: gocyclo\nfunc populateMetric(groupMetrics *GroupMetrics, key string, value float64) {\n\tlogrus.Debugf(\"key: %s\", key)\n\tswitch key {\n\tcase \"pitCPU\":\n\t\tgroupMetrics.PITCpu = value\n\tcase \"pitMemory\":\n\t\tgroupMetrics.PITMemory = value\n\tcase \"pitStorage\":\n\t\tgroupMetrics.PITStorage = value\n\tcase \"pitCPULimit\":\n\t\tgroupMetrics.PITCpuLimit = value\n\tcase \"pitMemoryLimit\":\n\t\tgroupMetrics.PITMemoryLimit = value\n\tcase \"mtdCPU\":\n\t\tgroupMetrics.MTDCpu = value\n\tcase \"mtdMemory\":\n\t\tgroupMetrics.MTDMemory = value\n\tcase \"mtdStorage\":\n\t\tgroupMetrics.MTDStorage = value\n\tcase \"mtdCPULimit\":\n\t\tgroupMetrics.MTDCpuLimit = value\n\tcase \"mtdMemoryLimit\":\n\t\tgroupMetrics.MTDMemoryLimit = value\n\tcase \"cpuCost\":\n\t\tgroupMetrics.CostCPU = value\n\tcase \"memoryCost\":\n\t\tgroupMetrics.CostMemory = value\n\tcase \"storageCost\":\n\t\tgroupMetrics.CostStorage = value\n\tcase \"cpuCostPerHour\":\n\t\tgroupMetrics.CostCPUPerHour = value\n\tcase \"memoryCostPerHour\":\n\t\tgroupMetrics.CostMemoryPerHour = value\n\tcase \"storageCostPerHour\":\n\t\tgroupMetrics.CostStoragePerHour = value\n\tcase \"lastMonthCPUCost\":\n\t\tgroupMetrics.LastMonthCPUCost = value\n\tcase \"lastMonthMemoryCost\":\n\t\tgroupMetrics.LastMonthMemoryCost = value\n\tcase \"lastMonthStorageCost\":\n\t\tgroupMetrics.LastMonthStorageCost = value\n\tcase \"lastLastMonthCPUCost\":\n\t\tgroupMetrics.LastLastMonthCPUCost = value\n\tcase \"lastLastMonthMemoryCost\":\n\t\tgroupMetrics.LastLastMonthMemoryCost = value\n\tcase \"lastLastMonthStorageCost\":\n\t\tgroupMetrics.LastLastMonthStorageCost = value\n\tcase \"livePods\":\n\t\tgroupMetrics.PodsCount = int(value)\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/group_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar dummyGroup models.Group\n\nfunc mockMetricsMap(key string, value float64) map[string]float64 {\n\tmetrics := make(map[string]float64)\n\tmetrics[key] = value\n\treturn metrics\n}\n\nfunc mockDgraphForGroupQueries(queryType string) {\n\texecuteQuery = func(query string, root interface{}) error {\n\t\tif queryType == testRetrieveAllGroups {\n\t\t\tdummyGroupList, ok := root.(*groupsRoot)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t\t}\n\t\t\tdummyGroup = models.Group{\n\t\t\t\tName:           \"group-purser\",\n\t\t\t\tPodsCount:      3,\n\t\t\t\tMtdCPU:         50.1,\n\t\t\t\tMtdMemory:      31.7,\n\t\t\t\tMtdStorage:     300,\n\t\t\t\tCPU:            4.1,\n\t\t\t\tMemory:         3.4,\n\t\t\t\tStorage:        10,\n\t\t\t\tMtdCPUCost:     5.01,\n\t\t\t\tMtdMemoryCost:  3.17,\n\t\t\t\tMtdStorageCost: 3,\n\t\t\t\tMtdCost:        11.18,\n\t\t\t}\n\t\t\tdummyGroupList.Groups = []models.Group{dummyGroup}\n\t\t\treturn nil\n\t\t} else if queryType == testRetrieveGroupMetrics {\n\t\t\tgroupMetrics, ok := root.(*groupJSONMetrics)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t\t}\n\t\t\tvar jsonMetrics []map[string]float64\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"pitCPU\", 1.3))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"pitMemory\", 2.4))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"pitStorage\", 2))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"pitCPULimit\", 1.4))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"pitMemoryLimit\", 2.5))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"mtdCPU\", 13.1))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"mtdMemory\", 24.2))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"mtdStorage\", 20))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"mtdCPULimit\", 14.1))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"mtdMemoryLimit\", 25.2))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"cpuCost\", 1.31))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"memoryCost\", 2.42))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"storageCost\", 0.21))\n\t\t\tjsonMetrics = append(jsonMetrics, mockMetricsMap(\"livePods\", 2))\n\t\t\tgroupMetrics.JSONMetrics = jsonMetrics\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"no data found\")\n\t}\n}\n\n// TestRetrieveGroupsDataWithDgraphError ...\nfunc TestRetrieveGroupsDataWithDgraphError(t *testing.T) {\n\tmockDgraphForGroupQueries(testWrongQuery)\n\t_, err := RetrieveGroupsData()\n\tassert.Error(t, err)\n}\n\n// TestRetrieveGroupsData ...\nfunc TestRetrieveGroupsData(t *testing.T) {\n\tmockDgraphForGroupQueries(testRetrieveAllGroups)\n\tgot, err := RetrieveGroupsData()\n\texpected := []models.Group{{\n\t\tName:           \"group-purser\",\n\t\tPodsCount:      3,\n\t\tMtdCPU:         50.1,\n\t\tMtdMemory:      31.7,\n\t\tMtdStorage:     300,\n\t\tCPU:            4.1,\n\t\tMemory:         3.4,\n\t\tStorage:        10,\n\t\tMtdCPUCost:     5.01,\n\t\tMtdMemoryCost:  3.17,\n\t\tMtdStorageCost: 3,\n\t\tMtdCost:        11.18,\n\t}}\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, got)\n}\n\n// TestGroupMetricsFromPodUIDsWithDgraphError ...\nfunc TestGroupMetricsFromPodUIDsWithDgraphError(t *testing.T) {\n\tmockDgraphForGroupQueries(testWrongQuery)\n\t_, err := RetrieveGroupMetricsFromPodUIDs(\"\")\n\tassert.Error(t, err)\n}\n\n// TestGroupMetricsFromPodUIDs ...\nfunc TestGroupMetricsFromPodUIDs(t *testing.T) {\n\tmockDgraphForGroupQueries(testRetrieveGroupMetrics)\n\tgot, err := RetrieveGroupMetricsFromPodUIDs(testPodUIDList)\n\texpected := GroupMetrics{\n\t\tPITCpu:         1.3,\n\t\tPITMemory:      2.4,\n\t\tPITStorage:     2,\n\t\tPITCpuLimit:    1.4,\n\t\tPITMemoryLimit: 2.5,\n\t\tMTDCpu:         13.1,\n\t\tMTDMemory:      24.2,\n\t\tMTDStorage:     20,\n\t\tMTDCpuLimit:    14.1,\n\t\tMTDMemoryLimit: 25.2,\n\t\tCostCPU:        1.31,\n\t\tCostMemory:     2.42,\n\t\tCostStorage:    0.21,\n\t\tPodsCount:      2,\n\t}\n\tassert.Equal(t, expected, got)\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/helpers.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n)\n\nvar secondsFromFirstOfCurrentMonth = getSecondsSinceMonthStart\n\nfunc getSecondsSinceMonthStart() string {\n\treturn fmt.Sprintf(\"%f\", utils.GetSecondsSince(utils.GetCurrentMonthStartTime()))\n}\n\nfunc getSecondsSinceForOtherMonths() map[string]string {\n\tsecondsSince := make(map[string]string)\n\tsecondsInAverageMonth := 2592000.0 // 30 * 24 * 60 * 60\n\tsecondsSinceCurrentMonthStart := utils.GetSecondsSince(utils.GetCurrentMonthStartTime())\n\tsecondsSinceLastMonthStart := secondsSinceCurrentMonthStart + secondsInAverageMonth\n\tsecondsSinceLastLastMonthStart := secondsSinceCurrentMonthStart + 2*secondsInAverageMonth\n\tsecondsSinceLastMonthEnd := secondsSinceCurrentMonthStart - 1.0\n\tsecondsSinceLastLastMonthEnd := secondsSinceLastMonthStart - 1.0\n\tsecondsSince[\"currentMonthStart\"] = fmt.Sprintf(\"%f\", secondsSinceCurrentMonthStart)\n\tsecondsSince[\"lastMonthStart\"] = fmt.Sprintf(\"%f\", secondsSinceLastMonthStart)\n\tsecondsSince[\"lastMonthEnd\"] = fmt.Sprintf(\"%f\", secondsSinceLastMonthEnd)\n\tsecondsSince[\"lastLastMonthStart\"] = fmt.Sprintf(\"%f\", secondsSinceLastLastMonthStart)\n\tsecondsSince[\"lastLastMonthEnd\"] = fmt.Sprintf(\"%f\", secondsSinceLastLastMonthEnd)\n\treturn secondsSince\n}\n\nfunc getQueryForMetricsComputationWithAliasAndVariables(suffix string) string {\n\treturn `name\n\t\t\ttype\n\t\t\tcpu: cpu` + suffix + ` as cpuRequest\n\t\t\tmemory: memory` + suffix + ` as memoryRequest\n\t\t\tstorage: storage` + suffix + ` as storageRequest\n\t\t\t` + getQueryForTimeComputation(suffix) + `\n\t\t\t` + getQueryForCostWithPriceWithAliasAndVariables(suffix)\n}\n\nfunc getQueryForMetricsComputationWithAlias(suffix string) string {\n\treturn `name\n\t\t\ttype\n\t\t\tcpu: cpu` + suffix + ` as cpuRequest\n\t\t\tmemory: memory` + suffix + ` as memoryRequest\n\t\t\tstorage: storage` + suffix + ` as storageRequest\n\t\t\t` + getQueryForTimeComputation(suffix) + `\n\t\t\t` + getQueryForCostWithPriceWithAlias(suffix)\n}\n\nfunc getQueryForMetricsComputation(suffix string) string {\n\treturn `cpu` + suffix + ` as cpuRequest\n\t\t\tmemory` + suffix + ` as memoryRequest\n\t\t\tstorage` + suffix + ` as storageRequest\n\t\t\t` + getQueryForTimeComputation(suffix) + `\n\t\t\t` + getQueryForCostWithPrice(suffix)\n}\n\nfunc getQueryForTimeComputation(suffix string) string {\n\tsecondsSinceMonthStart := secondsFromFirstOfCurrentMonth()\n\treturn `st` + suffix + ` as startTime\n\t\t\tstSeconds` + suffix + ` as math(since(st` + suffix + `))\n\t\t\tsecondsSinceStart` + suffix + ` as math(cond(stSeconds` + suffix + ` > ` + secondsSinceMonthStart + `, ` + secondsSinceMonthStart + `, stSeconds` + suffix + `))\n\t\t\tet` + suffix + ` as endTime\n\t\t\tisTerminated` + suffix + ` as count(endTime)\n\t\t\tsecondsSinceEnd` + suffix + ` as math(cond(isTerminated` + suffix + ` == 0, 0.0, since(et` + suffix + `)))\n\t\t\tdurationInHours` + suffix + ` as math(cond(secondsSinceStart` + suffix + ` > secondsSinceEnd` + suffix + `, (secondsSinceStart` + suffix + ` - secondsSinceEnd` + suffix + `) / 3600, 0.0))`\n}\n\nfunc getQueryForCostWithPriceWithAliasAndVariables(suffix string) string {\n\treturn `pricePerCPU` + suffix + ` as cpuPrice\n\t\t\tpricePerMemory` + suffix + ` as memoryPrice\n\t\t\tcpuCost: cpuCost` + suffix + ` as math(cpu` + suffix + ` * durationInHours` + suffix + ` * pricePerCPU` + suffix + `)\n\t\t\tmemoryCost: memoryCost` + suffix + ` as math(memory` + suffix + ` * durationInHours` + suffix + ` * pricePerMemory` + suffix + `)\n\t\t\tstorageCost: storageCost` + suffix + ` as math(storage` + suffix + ` * durationInHours` + suffix + ` * ` + models.DefaultStorageCostPerGBPerHour + `)`\n}\n\nfunc getQueryForCostWithPriceWithAlias(suffix string) string {\n\treturn `pricePerCPU` + suffix + ` as cpuPrice\n\t\t\tpricePerMemory` + suffix + ` as memoryPrice\n\t\t\tcpuCost: math(cpu` + suffix + ` * durationInHours` + suffix + ` * pricePerCPU` + suffix + `)\n\t\t\tmemoryCost: math(memory` + suffix + ` * durationInHours` + suffix + ` * pricePerMemory` + suffix + `)\n\t\t\tstorageCost: math(storage` + suffix + ` * durationInHours` + suffix + ` * ` + models.DefaultStorageCostPerGBPerHour + `)`\n}\n\nfunc getQueryForCostWithPrice(suffix string) string {\n\treturn `pricePerCPU` + suffix + ` as cpuPrice\n\t\t\tpricePerMemory` + suffix + ` as memoryPrice\n\t\t\tcpuCost` + suffix + ` as math(cpu` + suffix + ` * durationInHours` + suffix + ` * pricePerCPU` + suffix + `)\n\t\t\tmemoryCost` + suffix + ` as math(memory` + suffix + ` * durationInHours` + suffix + ` * pricePerMemory` + suffix + `)\n\t\t\tstorageCost` + suffix + ` as math(storage` + suffix + ` * durationInHours` + suffix + ` * ` + models.DefaultStorageCostPerGBPerHour + `)`\n}\n\nfunc getQueryForAggregatingChildMetricsWithAlias(childSuffix string) string {\n\treturn `name\n\t\t\ttype\n\t\t\tcpu: sum(val(cpu` + childSuffix + `))\n\t\t\tmemory: sum(val(memory` + childSuffix + `))\n\t\t\tstorage: sum(val(storage` + childSuffix + `))\n\t\t\tcpuCost: sum(val(cpuCost` + childSuffix + `))\n\t\t\tmemoryCost: sum(val(memoryCost` + childSuffix + `))\n\t\t\tstorageCost: sum(val(storageCost` + childSuffix + `))`\n}\n\nfunc getQueryForAggregatingChildMetrics(parentSuffix, childSuffix string) string {\n\treturn `cpu` + parentSuffix + ` as sum(val(cpu` + childSuffix + `))\n\t\t\tmemory` + parentSuffix + ` as sum(val(memory` + childSuffix + `))\n\t\t\tstorage` + parentSuffix + ` as sum(val(storage` + childSuffix + `))\n\t\t\tcpuCost` + parentSuffix + ` as sum(val(cpuCost` + childSuffix + `))\n\t\t\tmemoryCost` + parentSuffix + ` as sum(val(memoryCost` + childSuffix + `))\n\t\t\tstorageCost` + parentSuffix + ` as sum(val(storageCost` + childSuffix + `))`\n}\n\nfunc getQueryFromSubQueryWithAlias(suffix string) string {\n\treturn `name\n\t\t\ttype\n\t\t\tcpu: val(cpu` + suffix + `)\n\t\t\tmemory: val(memory` + suffix + `)\n\t\t\tstorage: val(storage` + suffix + `)\n\t\t\tcpuCost: val(cpuCost` + suffix + `)\n\t\t\tmemoryCost: val(memoryCost` + suffix + `)\n\t\t\tstorageCost: val(storageCost` + suffix + `)`\n}\n\nfunc (r *Resource) getQueryForPodParentMetrics() string {\n\treturn `query {\n\t\tparent(func: has(` + r.Check + `)) @filter(eq(name, \"` + r.Name + `\")) {\n\t\t\tchildren: ~` + r.Type + ` @filter(has(isPod)) {\n\t\t\t\t` + getQueryForMetricsComputationWithAliasAndVariables(\"Pod\") + `\n\t\t\t}\n\t\t\t` + getQueryForAggregatingChildMetricsWithAlias(\"Pod\") + `\n\t\t}\n\t}`\n}\n\nfunc (r *Resource) getQueryForHierarchy() string {\n\treturn `query {\n\t\tparent(func: has(` + r.Check + `)) @filter(eq(name, \"` + r.Name + `\")) {\n\t\t\tname\n\t\t\ttype\n\t\t\tchildren: ~` + r.Type + ` ` + r.ChildFilter + ` {\n\t\t\t\tname\n\t\t\t\ttype\n\t\t\t}\n\t\t}\n\t}`\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/helpers_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// TestGetSecondsSinceMonthStart ...\nfunc TestGetSecondsSinceMonthStart(t *testing.T) {\n\tmaxSecondsInAMonth := 2678400.0\n\tgot := getSecondsSinceMonthStart()\n\tgotFloat, err := strconv.ParseFloat(got, 64)\n\tassert.NoError(t, err, \"unable to convert secondsSinceMonthStart to float64\")\n\tassert.False(t, gotFloat > maxSecondsInAMonth, \"secondsSinceMonthStart can't be greater than 2678400\")\n\tassert.False(t, gotFloat < 0, \"secondsSinceMonthStart can't be less than 0\")\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/label.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\n// CreateFilterFromListOfLabels will return a filter logic like\n// (eq(key, \"k1\") AND eq(value, \"v1\")) OR (eq(key, \"k1\") AND eq(value, \"v1\")) OR (eq(key, \"k1\") AND eq(value, \"v1\"))\nfunc CreateFilterFromListOfLabels(labels map[string][]string) string {\n\tseparator := \" OR \"\n\tvar filter string\n\tisFirst := true\n\tfor key, values := range labels {\n\t\tfor _, value := range values {\n\t\t\tif !isFirst {\n\t\t\t\tfilter += separator\n\t\t\t} else {\n\t\t\t\tisFirst = false\n\t\t\t}\n\t\t\tfilter += createFilterFromLabel(key, value)\n\t\t}\n\t}\n\treturn filter\n}\n\n// createFilterFromLabel takes key: k1, value: v1 and returns (eq(key, \"k1\") AND eq(value, \"v1\"))\nfunc createFilterFromLabel(key, value string) string {\n\treturn `(eq(key, \"` + key + `\") AND eq(value, \"` + value + `\"))`\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/label_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"testing\"\n\n\t\"github.com/vmware/purser/test/utils\"\n)\n\n// TestCreateFilterForLabel ...\nfunc TestCreateFilterFromLabel(t *testing.T) {\n\tgot := createFilterFromLabel(\"k1\", \"v1\")\n\texpected := `(eq(key, \"k1\") AND eq(value, \"v1\"))`\n\tutils.Equals(t, expected, got)\n}\n\n// TestCreateFilterFromListOfLabels ...\nfunc TestCreateFilterFromListOfLabels(t *testing.T) {\n\tlabels := make(map[string][]string)\n\tlabels[\"k1\"] = []string{\"v1\"}\n\tgot := CreateFilterFromListOfLabels(labels)\n\texpected := `(eq(key, \"k1\") AND eq(value, \"v1\"))`\n\tutils.Equals(t, expected, got)\n\n\tlabels[\"k2\"] = []string{\"v2\"}\n\tgot2 := CreateFilterFromListOfLabels(labels)\n\texpected1 := `(eq(key, \"k2\") AND eq(value, \"v2\")) OR (eq(key, \"k1\") AND eq(value, \"v1\"))`\n\texpected2 := `(eq(key, \"k1\") AND eq(value, \"v1\")) OR (eq(key, \"k2\") AND eq(value, \"v2\"))`\n\tutils.Assert(t, (got2 == expected1) || (got2 == expected2), \"label filter didn't match\")\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/login.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\n// Authenticate performs user authentication for service access\nfunc Authenticate(username, inputPassword string) bool {\n\tif !validateUsername(username) {\n\t\treturn false\n\t}\n\tlogin, err := getLoginCredentials(username)\n\tif err != nil {\n\t\tlogrus.Error(err)\n\t\treturn false\n\t}\n\treturn comparePasswords(login.Password, []byte(inputPassword))\n}\n\n// UpdatePassword updates stored password with new one for the given username in Dgraph\nfunc UpdatePassword(username, oldPassword, newPassword string) bool {\n\tif Authenticate(username, oldPassword) {\n\t\tlogin, err := getLoginCredentials(username)\n\t\tif err != nil {\n\t\t\tlogrus.Error(err)\n\t\t\treturn false\n\t\t}\n\t\tif err = hashAndUpdatePassword(&login, newPassword); err == nil {\n\t\t\treturn true\n\t\t}\n\t\tlogrus.Error(err)\n\t}\n\treturn false\n}\n\nfunc hashAndUpdatePassword(login *dgraph.Login, newPassword string) error {\n\thashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogin.Password = string(hashedPassword)\n\t_, err = dgraph.MutateNode(login, dgraph.UPDATE)\n\treturn err\n}\n\n// getLoginCredentials returns a struct of hashed password and username.\nfunc getLoginCredentials(username string) (dgraph.Login, error) {\n\tq := `query {\n\t\tlogin(func: has(isLogin)) @filter(eq(username, ` + username + `)) {\n\t\t\tuid\n\t\t\tusername\n\t\t\tpassword\n\t\t}\n\t}`\n\ttype root struct {\n\t\tLoginList []dgraph.Login `json:\"login\"`\n\t}\n\tnewRoot := root{}\n\tif err := executeQuery(q, &newRoot); err != nil || newRoot.LoginList == nil {\n\t\treturn dgraph.Login{}, err\n\t}\n\treturn newRoot.LoginList[0], nil\n}\n\nfunc validateUsername(username string) bool {\n\treturn username == \"admin\"\n}\n\nfunc comparePasswords(hashedPwd string, plainPwd []byte) bool {\n\tbyteHash := []byte(hashedPwd)\n\tif err := bcrypt.CompareHashAndPassword(byteHash, plainPwd); err != nil {\n\t\tlogrus.Error(err)\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/pod.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\ntype podRoot struct {\n\tPods []models.Pod `json:\"pods\"`\n}\n\n// RetrieveAllLivePods will return all pods without endTime in dgraph. Error is returned if any\n// failure is encountered in the process.\nfunc RetrieveAllLivePods() []models.Pod {\n\tquery := getAllLivePodsQuery()\n\tnewRoot := podRoot{}\n\terr := executeQuery(query, &newRoot)\n\tif err != nil {\n\t\tlogrus.Errorf(\"unable to retrieve all live pods: %v\", err)\n\t\treturn nil\n\t}\n\treturn newRoot.Pods\n}\n\n// RetrievePodsInteractions returns inbound and outbound interactions of a pod\nfunc RetrievePodsInteractions(name string, isOrphan bool) []byte {\n\tvar query string\n\tif name == All {\n\t\tif isOrphan {\n\t\t\tquery = `query {\n\t\t\t\tpods(func: has(isPod)) {\n\t\t\t\t\tname\n\t\t\t\t\toutbound: pod {\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t\tinbound: ~pod @filter(has(isPod)) {\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t} else {\n\t\t\tquery = `query {\n\t\t\t\tpods(func: has(isPod)) @filter(has(pod)) {\n\t\t\t\t\tname\n\t\t\t\t\toutbound: pod {\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t\tinbound: ~pod @filter(has(isPod)) {\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t}\n\t} else {\n\t\tquery = `query {\n\t\t\tpods(func: has(isPod)) @filter(eq(name, \"` + name + `\")) {\n\t\t\t\tname\n\t\t\t\toutbound: pod {\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tinbound: ~pod @filter(has(isPod)) {\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t}\n\t\t}`\n\t}\n\n\tresult, err := executeQueryRaw(query)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Error while retrieving query for pods interactions. Name: (%v), isOrphan: (%v), error: (%v)\", name, isOrphan, err)\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc getPricePerResourceForPod(name string) (float64, float64) {\n\tquery := `query {\n\t\tpods(func: has(isPod)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tcpuPrice\n\t\t\tmemoryPrice\n\t\t}\n\t}`\n\tnewRoot := podRoot{}\n\terr := executeQuery(query, &newRoot)\n\tif err != nil || len(newRoot.Pods) < 1 {\n\t\tlogrus.Errorf(\"err: %v\", err)\n\t\treturn models.DefaultCPUCostInFloat64, models.DefaultMemCostInFloat64\n\t}\n\tpod := newRoot.Pods[0]\n\treturn pod.CPUPrice, pod.MemoryPrice\n}\n\n// RetrievePodsInteractionsForAllLivePodsWithCount returns all pods in the dgraph\nfunc RetrievePodsInteractionsForAllLivePodsWithCount() ([]models.Pod, error) {\n\tq := `query {\n\t\tpods(func: has(isPod)) @filter((NOT has(endTime))) {\n\t\t\tname\n\t\t\tpod {\n\t\t\t\tname\n\t\t\t\tcount\n\t\t\t}\n\t\t\tcid: ~pod @filter(has(isService)) {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tPods []models.Pod `json:\"pods\"`\n\t}\n\tnewRoot := root{}\n\terr := executeQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newRoot.Pods, nil\n}\n\n// RetrievePodsUIDsByLabelsFilter returns pods satisfying the filter conditions for labels (OR logic only)\nfunc RetrievePodsUIDsByLabelsFilter(labelFilter string) ([]string, error) {\n\tq := getQueryForPodsWithLabelFilter(labelFilter)\n\tnewRoot := podRoot{}\n\terr := executeQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn removeDuplicates(newRoot.Pods), nil\n}\n\nfunc removeDuplicates(pods []models.Pod) []string {\n\tduplicateChecker := make(map[string]bool)\n\tvar podsUIDs []string\n\tfor _, pod := range pods {\n\t\tif _, isPresent := duplicateChecker[pod.UID]; !isPresent {\n\t\t\tpodsUIDs = append(podsUIDs, pod.UID)\n\t\t\tduplicateChecker[pod.UID] = true\n\t\t}\n\t}\n\treturn podsUIDs\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/pod_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\nfunc mockDgraphForPodQueries(queryType string) {\n\texecuteQuery = func(query string, root interface{}) error {\n\t\tif queryType == testLabelFilterPods {\n\t\t\tdummyPodList, ok := root.(*podRoot)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t\t}\n\t\t\tdummyPodList.Pods = []models.Pod{\n\t\t\t\t{\n\t\t\t\t\tID:   dgraph.ID{UID: testPodUID},\n\t\t\t\t\tName: testPodName,\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn nil\n\t\t} else if queryType == testAlivePods {\n\t\t\tdummyPodList, ok := root.(*podRoot)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t\t}\n\t\t\tdummyPodList.Pods = []models.Pod{\n\t\t\t\t{\n\t\t\t\t\tID:   dgraph.ID{UID: testPodUID, Xid: testPodXID},\n\t\t\t\t\tName: testPodName,\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"no data found\")\n\t}\n\n\texecuteQueryRaw = func(query string) ([]byte, error) {\n\t\treturn nil, fmt.Errorf(\"pod interactions err\")\n\t}\n}\n\n// TestRetrievePodsUIDsByLabelsFilterWithError ...\nfunc TestRetrievePodsUIDsByLabelsFilterWithError(t *testing.T) {\n\tmockDgraphForPodQueries(testWrongQuery)\n\n\t// input setup\n\tlabels := make(map[string][]string)\n\tlabels[\"k1\"] = []string{\"v1\"}\n\tinputLabelFilter := CreateFilterFromListOfLabels(labels)\n\n\t_, err := RetrievePodsUIDsByLabelsFilter(inputLabelFilter)\n\tassert.Error(t, err)\n}\n\n// TestRetrievePodsUIDsByLabelsFilter ...\nfunc TestRetrievePodsUIDsByLabelsFilter(t *testing.T) {\n\tmockDgraphForPodQueries(testLabelFilterPods)\n\n\t// input setup\n\tlabels := make(map[string][]string)\n\tlabels[\"k1\"] = []string{\"v1\"}\n\tinputLabelFilter := CreateFilterFromListOfLabels(labels)\n\n\tgot, err := RetrievePodsUIDsByLabelsFilter(inputLabelFilter)\n\texpected := []string{testPodUID}\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveAllLivePodsWithDgraphError ...\nfunc TestRetrieveAllLivePodsWithDgraphError(t *testing.T) {\n\tmockDgraphForPodQueries(testWrongQuery)\n\tgot := RetrieveAllLivePods()\n\tassert.Nil(t, got)\n}\n\n// TestRetrieveAllLivePods ...\nfunc TestRetrieveAllLivePods(t *testing.T) {\n\tmockDgraphForPodQueries(testAlivePods)\n\tgot := RetrieveAllLivePods()\n\texpected := []models.Pod{\n\t\t{\n\t\t\tID:   dgraph.ID{UID: testPodUID, Xid: testPodXID},\n\t\t\tName: testPodName,\n\t\t},\n\t}\n\tassert.Equal(t, expected, got)\n}\n\nfunc TestPodInteractionsErrorCase(t *testing.T) {\n\tmockDgraphForPodQueries(testPodInteractions)\n\tgotAllOrphan := RetrievePodsInteractions(\"\", true)\n\tgotAllNonOrphan := RetrievePodsInteractions(\"\", false)\n\tgotWithName := RetrievePodsInteractions(testPodName, false)\n\t_, err := RetrievePodsInteractionsForAllLivePodsWithCount()\n\tassert.Nil(t, gotAllOrphan)\n\tassert.Nil(t, gotAllNonOrphan)\n\tassert.Nil(t, gotWithName)\n\tassert.Error(t, err)\n}\n\nfunc TestGetPricePerResourceForPodWithError(t *testing.T) {\n\tmockDgraphForResourceQueries(testWrongQuery, testPodName, PodType)\n\tgotCPUPrice, gotMemoryPrice := getPricePerResourceForPod(testPodName)\n\texpectedCPUPrice, expectedMemoryPrice := models.DefaultCPUCostInFloat64, models.DefaultMemCostInFloat64\n\tassert.Equal(t, expectedCPUPrice, gotCPUPrice)\n\tassert.Equal(t, expectedMemoryPrice, gotMemoryPrice)\n}\n\nfunc TestGetPricePerResourceForPod(t *testing.T) {\n\tmockDgraphForResourceQueries(testPodPrices, testPodName, PodType)\n\tgotCPUPrice, gotMemoryPrice := getPricePerResourceForPod(testPodName)\n\texpectedCPUPrice, expectedMemoryPrice := testCPUPrice, testMemoryPrice\n\tassert.Equal(t, expectedCPUPrice, gotCPUPrice)\n\tassert.Equal(t, expectedMemoryPrice, gotMemoryPrice)\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/queries.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\n// DeploymentMetrics query\nfunc getQueryForDeploymentMetrics(name string) string {\n\treturn `query {\n\t\tdep as var(func: has(isDeployment)) @filter(eq(name, \"` + name + `\")) {\n\t\t\t~deployment @filter(has(isReplicaset)) {\n\t\t\t\t~replicaset @filter(has(isPod)) {\n\t\t\t\t\t` + getQueryForMetricsComputation(\"ReplicasetPod\") + `\n\t\t\t\t}\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"DeploymentReplicaset\", \"ReplicasetPod\") + `\n\t\t\t}\n\t\t\t` + getQueryForAggregatingChildMetrics(\"Deployment\", \"DeploymentReplicaset\") + `\n\t\t}\n\n\t\tparent(func: uid(dep)) {\n\t\t\tchildren: ~deployment @filter(has(isReplicaset)) {\n\t\t\t\t` + getQueryFromSubQueryWithAlias(\"DeploymentReplicaset\") + `\n\t\t\t}\n\t\t\t` + getQueryFromSubQueryWithAlias(\"Deployment\") + `\n\t\t}\n\t}`\n}\n\n// PodMetrics query\nfunc getQueryForPodMetrics(name, cpuPrice, memoryPrice string) string {\n\treturn `query {\n\t\tparent(func: has(isPod)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tchildren: ~pod @filter(has(isContainer)) {\n\t\t\t\tname\n\t\t\t\ttype\n\t\t\t\t` + getQueryForTimeComputation(\"Container\") + `\n\t\t\t\tcpu: cpu as cpuRequest\n\t\t\t\tmemory: memory as memoryRequest\n\t\t\t\tcpuCost: math(cpu * durationInHoursContainer * ` + cpuPrice + `)\n\t\t\t\tmemoryCost: math(memory * durationInHoursContainer * ` + memoryPrice + `)\n\t\t\t}\n\t\t\t` + getQueryForMetricsComputationWithAlias(\"Pod\") + `\n\t\t}\n\t}`\n}\n\n// ContainerMetrics query\nfunc getQueryForContainerMetrics(name string) string {\n\treturn `query {\n\t\tparent(func: has(isContainer)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tname\n\t\t\ttype\n\t\t\tcpu: cpu as cpuRequest\n\t\t\tmemory: memory as memoryRequest\n\t\t\t` + getQueryForTimeComputation(\"\") + `\n\t\t\tcpuCost: math(cpu * durationInHours * ` + models.DefaultCPUCostPerCPUPerHour + `)\n\t\t\tmemoryCost: math(memory * durationInHours * ` + models.DefaultMemCostPerGBPerHour + `)\n\t\t}\n\t}`\n}\n\n// PVMetrics query\nfunc getQueryForPVMetrics(name string) string {\n\treturn `query {\n\t\tparent(func: has(isPersistentVolume)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tchildren: ~pv @filter(has(isPersistentVolumeClaim)) {\n\t\t\t\tname\n\t\t\t\ttype\n\t\t\t\tstorage: pvcStorage as storageCapacity\n\t\t\t\t` + getQueryForTimeComputation(\"PVC\") + `\n\t\t\t\tstorageCost: math(pvcStorage * durationInHoursPVC * ` + models.DefaultStorageCostPerGBPerHour + `)\n\t\t\t}\n\t\t\tname\n\t\t\ttype\n\t\t\tstorage: storage as storageCapacity\n\t\t\tstorageCapacity\n\t\t\t` + getQueryForTimeComputation(\"\") + `\n\t\t\tstorageCost: math(storage * durationInHours * ` + models.DefaultStorageCostPerGBPerHour + `)\n\t\t\tstorageAllocated: sum(val(pvcStorage))\n        }\n    }`\n}\n\n// PVCMetrics query\nfunc getQueryForPVCMetrics(name string) string {\n\treturn `query {\n\t\tparent(func: has(isPersistentVolumeClaim)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tname\n\t\t\ttype\n\t\t\tstorage: storage as storageCapacity\n\t\t\t` + getQueryForTimeComputation(\"\") + `\n\t\t\tstorageCost: math(storage * durationInHours * ` + models.DefaultStorageCostPerGBPerHour + `)\n        }\n    }`\n}\n\n// NodeMetrics query\nfunc getQueryForNodeMetrics(name string) string {\n\treturn `query {\n\t\tparent(func: has(isNode)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tchildren: ~node @filter(has(isPod)) {\n\t\t\t\t` + getQueryForMetricsComputationWithAlias(\"Pod\") + `\n\t\t\t}\n\t\t\tname\n\t\t\ttype\n\t\t\tcpu: cpu as cpuCapacity\n\t\t\tmemory: memory as memoryCapacity\n\t\t\tstorage: storage as sum(val(storagePod))\n\t\t\tcpuAllocated: sum(val(cpuPod))\n\t\t\tmemoryAllocated: sum(val(memoryPod))\n\t\t\tcpuCapacity\n\t\t\tmemoryCapacity\n\t\t\t` + getQueryForTimeComputation(\"\") + `\n\t\t\t` + getQueryForCostWithPriceWithAlias(\"\") + `\n\t\t}\n\t}`\n}\n\n// NamespaceMetrics query\nfunc getQueryForNamespaceMetrics(name string) string {\n\treturn `query {\n\t\tns as var(func: has(isNamespace)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tchilds as ~namespace @filter(has(isDeployment) OR has(isStatefulset) OR has(isJob) OR has(isDaemonset) OR (has(isReplicaset) AND (NOT has(deployment)))) {\n\t\t\t\tname\n\t\t\t\ttype\n\t\t\t\t~deployment @filter(has(isReplicaset)) {\n\t\t\t\t\tname\n\t\t\t\t\ttype\n\t\t\t\t\t~replicaset @filter(has(isPod)) {\n\t\t\t\t\t\t` + getQueryForMetricsComputation(\"ReplicasetPod\") + `\n\t\t\t        }\n\t\t\t\t\t` + getQueryForAggregatingChildMetrics(\"DeploymentReplicaset\", \"ReplicasetPod\") + `\n                }\n\t\t\t\t~statefulset @filter(has(isPod)) {\n\t\t\t\t\t` + getQueryForMetricsComputation(\"StatefulsetPod\") + `\n                }\n\t\t\t\t~job @filter(has(isPod)) {\n\t\t\t\t\t` + getQueryForMetricsComputation(\"JobPod\") + `\n                }\n\t\t\t\t~daemonset @filter(has(isPod)) {\n\t\t\t\t\t` + getQueryForMetricsComputation(\"DaemonsetPod\") + `\n                }\n\t\t\t\t~replicaset @filter(has(isPod)) {\n\t\t\t\t\t` + getQueryForMetricsComputation(\"ReplicasetSimplePod\") + `\n                }\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"SumReplicasetSimplePod\", \"ReplicasetSimplePod\") + `\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"SumDaemonsetPod\", \"DaemonsetPod\") + `\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"SumJobPod\", \"JobPod\") + `\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"SumStatefulsetPod\", \"StatefulsetPod\") + `\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"SumDeploymentReplicaset\", \"DeploymentReplicaset\") + `\n\t\t\t\tcpuNamespaceChild as math(cpu` + \"SumReplicasetSimplePod\" + ` + cpu` + \"SumDaemonsetPod\" + ` + cpu` + \"SumJobPod\" + ` + cpu` + \"SumStatefulsetPod\" + ` + cpu` + \"SumDeploymentReplicaset\" + `)\n\t\t\t\tmemoryNamespaceChild as math(memory` + \"SumReplicasetSimplePod\" + ` + memory` + \"SumDaemonsetPod\" + ` + memory` + \"SumJobPod\" + ` + memory` + \"SumStatefulsetPod\" + ` + memory` + \"SumDeploymentReplicaset\" + `)\n\t\t\t\tstorageNamespaceChild as math(storage` + \"SumReplicasetSimplePod\" + ` + storage` + \"SumDaemonsetPod\" + ` + storage` + \"SumJobPod\" + ` + storage` + \"SumStatefulsetPod\" + ` + storage` + \"SumDeploymentReplicaset\" + `)\n\t\t\t\tcpuCostNamespaceChild as math(cpuCost` + \"SumReplicasetSimplePod\" + ` + cpuCost` + \"SumDaemonsetPod\" + ` + cpuCost` + \"SumJobPod\" + ` + cpuCost` + \"SumStatefulsetPod\" + ` + cpuCost` + \"SumDeploymentReplicaset\" + `)\n\t\t\t\tmemoryCostNamespaceChild as math(memoryCost` + \"SumReplicasetSimplePod\" + ` + memoryCost` + \"SumDaemonsetPod\" + ` + memoryCost` + \"SumJobPod\" + ` + memoryCost` + \"SumStatefulsetPod\" + ` + memoryCost` + \"SumDeploymentReplicaset\" + `)\n\t\t\t\tstorageCostNamespaceChild as math(storageCost` + \"SumReplicasetSimplePod\" + ` + storageCost` + \"SumDaemonsetPod\" + ` + storageCost` + \"SumJobPod\" + ` + storageCost` + \"SumStatefulsetPod\" + ` + storageCost` + \"SumDeploymentReplicaset\" + `)\n\t\t\t}\n\t\t\t` + getQueryForAggregatingChildMetrics(\"Namespace\", \"NamespaceChild\") + `\n\t\t}\n\n\t\tparent(func: uid(ns)) {\n\t\t\tchildren: ~namespace @filter(uid(childs)) {\n\t\t\t\t` + getQueryFromSubQueryWithAlias(\"NamespaceChild\") + `\n\t\t\t}\n\t\t\t` + getQueryFromSubQueryWithAlias(\"Namespace\") + `\n        }\n    }`\n}\n\n// LogicalResourcesMetrics query\nfunc getMetricsQueryForLogicalResources() string {\n\treturn `query {\n\t\t\tns as var(func: has(isNamespace)) {\n\t\t\t\t~namespace @filter(has(isPod) AND (NOT has(endTime))) {\n\t\t\t\t\t` + getQueryForMetricsComputation(\"NamespacePod\") + `\n\t\t\t\t}\n\t\t\t\t` + getQueryForAggregatingChildMetrics(\"Namespace\", \"NamespacePod\") + `\n\t\t\t}\n\t\n\t\t\tchildren(func: uid(ns)) {\n\t\t\t\t` + getQueryFromSubQueryWithAlias(\"Namespace\") + `\n\t\t\t}\n\t\t}`\n}\n\n// PhysicalResourcesMetrics query\nfunc getMetricsQueryForPhysicalResources() string {\n\treturn `query {\n\t\t\tchildren(func: has(name)) @filter((has(isNode) OR has(isPersistentVolume)) AND (NOT has(endTime))) {\n\t\t\t\tname\n\t\t\ttype\n\t\t\tcpu: cpu as cpuCapacity\n\t\t\tmemory: memory as memoryCapacity\n\t\t\tstorage: storage as storageCapacity\n\t\t\t` + getQueryForTimeComputation(\"\") + `\n\t\t\t` + getQueryForCostWithPriceWithAlias(\"\") + `\n\t\t\t}\n\t\t}`\n}\n\n// LogicalResourcesHierarchy query\nfunc getHierarchyQueryForLogicalResource() string {\n\treturn `query {\n\t\t\tchildren(func: has(isNamespace)) {\n\t\t\t\tname\n\t\t\t\ttype\n\t\t\t}\n\t\t}`\n}\n\n// PhysicalResourcesHierarchy query\nfunc getHierarchyQueryForPhysicalResource() string {\n\treturn `query {\n\t\t\tchildren(func: has(name)) @filter(has(isNode) OR has(isPersistentVolume)) {\n\t\t\t\tname\n\t\t\t\ttype\n\t\t\t}\n\t\t}`\n}\n\n/*\nThe following functions are related to Queries for Custom Groups\n*/\n\nfunc getQueryForAllGroupsData() string {\n\treturn `query {\n\t\tgroups(func: has(isGroup)) {\n\t\t\tname\n\t\t\tpodsCount\n\t\t\tmtdCPU\n\t\t\tmtdMemory\n\t\t\tmtdStorage\n\t\t\tcpu\n\t\t\tmemory\n\t\t\tstorage\n\t\t\tmtdCPUCost\n\t\t\tmtdMemoryCost\n\t\t\tmtdStorageCost\n\t\t\tmtdCost\n\t\t\tprojectedCPUCost\n\t\t\tprojectedMemoryCost\n\t\t\tprojectedStorageCost\n\t\t\tprojectedCost\n\t\t\tlastMonthCPUCost\n\t\t\tlastMonthMemoryCost\n\t\t\tlastMonthStorageCost\n\t\t\tlastMonthCost\n\t\t\tlastLastMonthCPUCost\n\t\t\tlastLastMonthMemoryCost\n\t\t\tlastLastMonthStorageCost\n\t\t\tlastLastMonthCost\n\t\t}\n\t}`\n}\n\nfunc getQueryForGroupMetrics(podsUIDs string) string {\n\tsecondsSince := getSecondsSinceForOtherMonths()\n\treturn `query {\n\t\tvar(func: uid(` + podsUIDs + `)) {\n\t\t\tpodCpu as cpuRequest\n\t\t\tpodMemory as memoryRequest\n\t\t\tpvcStorage as storageRequest\n\t\t\tpodCpuLimit as cpuLimit\n\t\t\tpodMemoryLimit as memoryLimit\n\t\t\tcpuRequestCount as count(cpuRequest)\n\t\t\tmemoryRequestCount as count(memoryRequest)\n\t\t\tstorageRequestCount as count(storageRequest)\n\t\t\tcpuLimitCount as count(cpuLimit)\n\t\t\tmemoryLimitCount as count(memoryLimit)\n\t\t\tpodEndTime as endTime\n\t\t\tisTerminated as count(endTime)\n\t\t\tsecondsSincePodEndTime as math(cond(isTerminated == 0, 0.0, since(podEndTime)))\n\t\t\tpodStartTime as startTime\n\t\t\tsecondsSincePodStartTime as math(since(podStartTime))\n\t\t\tsecondsSinceCurrentMonthTrueStart as math(cond(secondsSincePodStartTime > ` + secondsSince[\"currentMonthStart\"] + `, ` + secondsSince[\"currentMonthStart\"] + `, secondsSincePodStartTime))\n\t\t\tcurrentMonthTrueDurationInHours as math(cond(secondsSinceCurrentMonthTrueStart > secondsSincePodEndTime, (secondsSinceCurrentMonthTrueStart - secondsSincePodEndTime)/3600, 0.0))\n\t\t\tsecondsSinceLastMonthTrueStart as math(cond(secondsSincePodStartTime > ` + secondsSince[\"lastMonthStart\"] + `, ` + secondsSince[\"lastMonthStart\"] + `, secondsSincePodStartTime))\n\t\t\tsecondsSinceLastMonthTrueEnd as math(cond(secondsSincePodEndTime > ` + secondsSince[\"lastMonthEnd\"] + `, secondsSincePodEndTime, ` + secondsSince[\"lastMonthEnd\"] + `))\n\t\t\tlastMonthTrueDurationInHours as math(cond(secondsSinceLastMonthTrueStart > secondsSinceLastMonthTrueEnd, (secondsSinceLastMonthTrueStart - secondsSinceLastMonthTrueEnd)/3600, 0.0))\n\t\t\tsecondsSinceLastLastMonthTrueStart as math(cond(secondsSincePodStartTime > ` + secondsSince[\"lastLastMonthStart\"] + `, ` + secondsSince[\"lastLastMonthStart\"] + `, secondsSincePodStartTime))\n\t\t\tsecondsSinceLastLastMonthTrueEnd as math(cond(secondsSincePodEndTime > ` + secondsSince[\"lastLastMonthEnd\"] + `, secondsSincePodEndTime, ` + secondsSince[\"lastLastMonthEnd\"] + `))\n\t\t\tlastLastMonthTrueDurationInHours as math(cond(secondsSinceLastLastMonthTrueStart > secondsSinceLastLastMonthTrueEnd, (secondsSinceLastLastMonthTrueStart - secondsSinceLastLastMonthTrueEnd)/3600, 0.0))\n\t\t\tisAlive as math(cond(isTerminated == 0, 1, 0))\n\t\t\tpitPodCPU as math(cond(isTerminated == 0, cond(cpuRequestCount > 0, podCpu, 0.0), 0.0))\n\t\t\tpitPodMemory as math(cond(isTerminated == 0, cond(memoryRequestCount > 0, podMemory, 0.0), 0.0))\n\t\t\tpitPvcStorage as math(cond(isTerminated == 0, cond(storageRequestCount > 0, pvcStorage, 0.0), 0.0))\n\t\t\tpitPodCPULimit as math(cond(isTerminated == 0, cond(cpuLimitCount > 0, podCpuLimit, 0.0), 0.0))\n\t\t\tpitPodMemoryLimit as math(cond(isTerminated == 0, cond(memoryLimitCount > 0, podMemoryLimit, 0.0), 0.0))\n\t\t\tmtdPodCPU as math(podCpu * currentMonthTrueDurationInHours)\n\t\t\tmtdPodMemory as math(podMemory * currentMonthTrueDurationInHours)\n\t\t\tmtdPvcStorage as math(pvcStorage * currentMonthTrueDurationInHours)\n\t\t\tmtdPodCPULimit as math(podCpuLimit * currentMonthTrueDurationInHours)\n\t\t\tmtdPodMemoryLimit as math(podMemoryLimit * currentMonthTrueDurationInHours)\n\t\t\tpricePerCPU as cpuPrice\n\t\t\tpricePerMemory as memoryPrice\n\t\t\tpodCpuCost as math(mtdPodCPU * pricePerCPU)\n\t\t\tpodMemoryCost as math(mtdPodMemory * pricePerMemory)\n\t\t\tpodStorageCost as math(mtdPvcStorage * ` + models.DefaultStorageCostPerGBPerHour + `)\n\t\t\tpodLiveCPUCostPerHour as math(pitPodCPU * pricePerCPU)\n\t\t\tpodLiveMemoryCostPerHour as math(pitPodMemory * pricePerMemory)\n\t\t\tpodLiveStorageCostPerHour as math(pitPvcStorage * ` + models.DefaultStorageCostPerGBPerHour + `)\n\t\t\tpodCPUCostPerHour as math(podCpu * pricePerCPU)\n\t\t\tpodMemoryCostPerHour as math(podMemory * pricePerMemory)\n\t\t\tpodStorageCostPerHour as math(pvcStorage * ` + models.DefaultStorageCostPerGBPerHour + `)\n\t\t\tpodCPUCostLastMonth as math(podCPUCostPerHour * lastMonthTrueDurationInHours)\n\t\t\tpodMemoryCostLastMonth as math(podMemoryCostPerHour * lastMonthTrueDurationInHours)\n\t\t\tpodStorageCostLastMonth as math(podStorageCostPerHour * lastMonthTrueDurationInHours)\n\t\t\tpodCPUCostLastLastMonth as math(podCPUCostPerHour * lastLastMonthTrueDurationInHours)\n\t\t\tpodMemoryCostLastLastMonth as math(podMemoryCostPerHour * lastLastMonthTrueDurationInHours)\n\t\t\tpodStorageCostLastLastMonth as math(podStorageCostPerHour * lastLastMonthTrueDurationInHours)\n\t\t}\n\t\t\n\t\tgroup() {\n\t\t\tpitCPU: sum(val(pitPodCPU))\n\t\t\tpitMemory: sum(val(pitPodMemory))\n\t\t\tpitStorage: sum(val(pitPvcStorage))\n\t\t\tpitCPULimit: sum(val(pitPodCPULimit))\n\t\t\tpitMemoryLimit: sum(val(pitPodMemoryLimit))\n\t\t\tmtdCPU: sum(val(mtdPodCPU))\n\t\t\tmtdMemory: sum(val(mtdPodMemory))\n\t\t\tmtdStorage: sum(val(mtdPvcStorage))\n\t\t\tmtdCPULimit: sum(val(mtdPodCPULimit))\n\t\t\tmtdMemoryLimit: sum(val(mtdPodMemoryLimit))\n\t\t\tcpuCost: sum(val(podCpuCost))\n\t\t\tmemoryCost: sum(val(podMemoryCost))\n\t\t\tstorageCost: sum(val(podStorageCost))\n\t\t\tcpuCostPerHour: sum(val(podLiveCPUCostPerHour))\n\t\t\tmemoryCostPerHour: sum(val(podLiveMemoryCostPerHour))\n\t\t\tstorageCostPerHour: sum(val(podLiveStorageCostPerHour))\n\t\t\tlastMonthCPUCost: sum(val(podCPUCostLastMonth))\n\t\t\tlastMonthMemoryCost: sum(val(podMemoryCostLastMonth))\n\t\t\tlastMonthStorageCost: sum(val(podStorageCostLastMonth))\n\t\t\tlastLastMonthCPUCost: sum(val(podCPUCostLastLastMonth))\n\t\t\tlastLastMonthMemoryCost: sum(val(podMemoryCostLastLastMonth))\n\t\t\tlastLastMonthStorageCost: sum(val(podStorageCostLastLastMonth))\n\t\t\tlivePods: sum(val(isAlive))\n\t\t}\n\t}`\n}\n\nfunc getQueryForSubscribersRetrieval() string {\n\treturn `query {\n\t\tsubscribers(func: has(isSubscriber)) @filter(NOT(has(endTime))) {\n\t\t\tname\n\t\t\tspec {\n\t\t\t\theaders\n\t\t\t\turl\n\t\t\t}\n\t\t}\n\t}`\n}\n\nfunc getAllLivePodsQuery() string {\n\treturn `query {\n\t\tpods(func: has(isPod)) @filter(NOT has(endTime)) {\n\t\t\tuid\n\t\t\txid\n\t\t\tname\n\t\t}\n\t}`\n}\n\nfunc getQueryForPodsWithLabelFilter(labelFilter string) string {\n\treturn `query {\n\t\tvar(func: has(isLabel)) @filter(` + labelFilter + `) {\n            podUIDs as ~label @filter(has(isPod)) {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t\tpods(func: uid(podUIDs)) {\n\t\t\tuid\n\t\t\tname\n\t\t}\n\t}`\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/resource.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/Sirupsen/logrus\"\n)\n\n// Cluster resource constants\nconst (\n\tContainerCheck = \"isContainer\"\n\tContainerType  = \"container\"\n\tIsProcFilter   = \"@filter(has(isProc))\"\n\n\tDaemonsetCheck = \"isDaemonset\"\n\tDaemonsetType  = \"daemonset\"\n\tIsPodFilter    = \"@filter(has(isPod))\"\n\n\tDeploymentCheck    = \"isDeployment\"\n\tDeploymentType     = \"deployment\"\n\tIsReplicasetFilter = \"@filter(has(isReplicaset))\"\n\n\tJobCheck = \"isJob\"\n\tJobType  = \"job\"\n\n\tNamespaceCheck       = \"isNamespace\"\n\tNamespaceType        = \"namespace\"\n\tNamespaceChildFilter = \"@filter(has(isDeployment) OR has(isStatefulset) OR has(isJob) OR has(isDaemonset) OR (has(isReplicaset) AND (NOT has(deployment))))\"\n\n\tNodeCheck = \"isNode\"\n\tNodeType  = \"node\"\n\n\tPodCheck          = \"isPod\"\n\tPodType           = \"pod\"\n\tIsContainerFilter = \"@filter(has(isContainer))\"\n\n\tPVCheck     = \"isPersistentVolume\"\n\tPVType      = \"pv\"\n\tIsPVCFilter = \"@filter(has(isPersistentVolumeClaim))\"\n\n\tPVCCheck = \"isPersistentVolumeClaim\"\n\tPVCType  = \"pvc\"\n\n\tReplicasetCheck = \"isReplicaset\"\n\tReplicasetType  = \"replicaset\"\n\n\tStatefulsetCheck = \"isStatefulset\"\n\tStatefulsetType  = \"statefulset\"\n)\n\n// Resource structure\ntype Resource struct {\n\tCheck       string\n\tType        string\n\tName        string\n\tChildFilter string\n}\n\n// RetrieveResourceHierarchy returns hierarchy for a given resource\nfunc (r *Resource) RetrieveResourceHierarchy() JSONDataWrapper {\n\tif r.Name == All {\n\t\tlogrus.Errorf(\"wrong type of query, empty name is given\")\n\t\treturn JSONDataWrapper{}\n\t}\n\tquery := r.getQueryForHierarchy()\n\treturn getJSONDataFromQuery(query)\n}\n\n// RetrieveResourceMetrics returns metrics for a given resource\nfunc (r *Resource) RetrieveResourceMetrics() JSONDataWrapper {\n\tif r.Name == All {\n\t\tlogrus.Errorf(\"wrong type of query, empty name is given\")\n\t\treturn JSONDataWrapper{}\n\t}\n\tquery := r.getQueryForResourceMetrics()\n\treturn getJSONDataFromQuery(query)\n}\n\nfunc (r *Resource) getQueryForResourceMetrics() string {\n\tswitch r.Type {\n\tcase DeploymentType:\n\t\treturn getQueryForDeploymentMetrics(r.Name)\n\tcase NamespaceType:\n\t\treturn getQueryForNamespaceMetrics(r.Name)\n\tcase NodeType:\n\t\treturn getQueryForNodeMetrics(r.Name)\n\tcase PVType:\n\t\treturn getQueryForPVMetrics(r.Name)\n\tcase PVCType:\n\t\treturn getQueryForPVCMetrics(r.Name)\n\tcase ContainerType:\n\t\treturn getQueryForContainerMetrics(r.Name)\n\tcase PodType:\n\t\tcpuPriceInFloat64, memoryPriceInFloat64 := getPricePerResourceForPod(r.Name)\n\t\tcpuPrice := strconv.FormatFloat(cpuPriceInFloat64, 'f', 11, 64)\n\t\tmemoryPrice := strconv.FormatFloat(memoryPriceInFloat64, 'f', 11, 64)\n\t\treturn getQueryForPodMetrics(r.Name, cpuPrice, memoryPrice)\n\t}\n\treturn r.getQueryForPodParentMetrics()\n}\n\n// getJSONDataFromQuery executes query and wraps the data in a desired structure(JSONDataWrapper)\nfunc getJSONDataFromQuery(query string) JSONDataWrapper {\n\tparentRoot := ParentWrapper{}\n\terr := executeQuery(query, &parentRoot)\n\tif err != nil || len(parentRoot.Parent) == 0 {\n\t\tlogrus.Errorf(\"Unable to execute query, err: (%v)\", err)\n\t\treturn JSONDataWrapper{}\n\t}\n\troot := JSONDataWrapper{\n\t\tData: parentRoot.Parent[0],\n\t}\n\treturn root\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/resource_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc mockDgraphForResourceQueries(queryType, resourceName, resourceType string) {\n\texecuteQuery = func(query string, root interface{}) error {\n\t\tif queryType == testPodPrices {\n\t\t\tnewRoot, ok := root.(*podRoot)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"wrong pod root received\")\n\t\t\t}\n\t\t\tpod := models.Pod{\n\t\t\t\tCPUPrice:    testCPUPrice,\n\t\t\t\tMemoryPrice: testMemoryPrice,\n\t\t\t}\n\t\t\tnewRoot.Pods = []models.Pod{pod}\n\t\t\treturn nil\n\t\t}\n\n\t\tdummyParentWrapper, ok := root.(*ParentWrapper)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t}\n\n\t\tvar parent ParentWrapper\n\t\tif queryType == testMetrics {\n\t\t\tfirstPodWithMetrics := Children{\n\t\t\t\tName:        \"pod-purser-1\",\n\t\t\t\tType:        PodType,\n\t\t\t\tCPU:         0.25,\n\t\t\t\tMemory:      0.1,\n\t\t\t\tStorage:     1.2,\n\t\t\t\tCPUCost:     0.024,\n\t\t\t\tMemoryCost:  0.09,\n\t\t\t\tStorageCost: 0.1,\n\t\t\t}\n\t\t\tsecondPodWithMetrics := Children{\n\t\t\t\tName:        \"pod-purser-2\",\n\t\t\t\tType:        PodType,\n\t\t\t\tCPU:         0.15,\n\t\t\t\tMemory:      0.2,\n\t\t\t\tStorage:     0.2,\n\t\t\t\tCPUCost:     0.014,\n\t\t\t\tMemoryCost:  0.19,\n\t\t\t\tStorageCost: 0.01,\n\t\t\t}\n\t\t\tparent = ParentWrapper{\n\t\t\t\tName:        resourceName,\n\t\t\t\tType:        resourceType,\n\t\t\t\tChildren:    []Children{firstPodWithMetrics, secondPodWithMetrics},\n\t\t\t\tCPU:         0.40,\n\t\t\t\tMemory:      0.28,\n\t\t\t\tStorage:     1.4,\n\t\t\t\tCPUCost:     0.038,\n\t\t\t\tMemoryCost:  0.28,\n\t\t\t\tStorageCost: 0.11,\n\t\t\t}\n\t\t\tdummyParentWrapper.Parent = []ParentWrapper{parent}\n\t\t\treturn nil\n\t\t} else if queryType == testHierarchy {\n\t\t\tfirstPod := Children{\n\t\t\t\tName: \"pod-purser-1\",\n\t\t\t\tType: PodType,\n\t\t\t}\n\t\t\tsecondPod := Children{\n\t\t\t\tName: \"pod-purser-2\",\n\t\t\t\tType: PodType,\n\t\t\t}\n\t\t\tparent = ParentWrapper{\n\t\t\t\tName:     resourceName,\n\t\t\t\tType:     resourceType,\n\t\t\t\tChildren: []Children{firstPod, secondPod},\n\t\t\t}\n\t\t\tdummyParentWrapper.Parent = []ParentWrapper{parent}\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"unable to retrieve data from dgraph\")\n\t}\n}\n\n// TestRetrieveResourceHierarchyWithNameEmpty ...\nfunc TestRetrieveResourceHierarchyWithNameEmpty(t *testing.T) {\n\tinput := &Resource{\n\t\tCheck:       DaemonsetCheck,\n\t\tType:        DaemonsetType,\n\t\tName:        \"\",\n\t\tChildFilter: IsPodFilter,\n\t}\n\tgot := input.RetrieveResourceHierarchy()\n\texpected := JSONDataWrapper{}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveResourceHierarchy ...\nfunc TestRetrieveResourceHierarchy(t *testing.T) {\n\tmockDgraphForResourceQueries(testHierarchy, testDaemonsetName, DaemonsetType)\n\n\tinput := &Resource{\n\t\tCheck:       DaemonsetCheck,\n\t\tType:        DaemonsetType,\n\t\tName:        testDaemonsetName,\n\t\tChildFilter: IsPodFilter,\n\t}\n\tgot := input.RetrieveResourceHierarchy()\n\n\tfirstPod := Children{\n\t\tName: \"pod-purser-1\",\n\t\tType: PodType,\n\t}\n\tsecondPod := Children{\n\t\tName: \"pod-purser-2\",\n\t\tType: PodType,\n\t}\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:     testDaemonsetName,\n\t\t\tType:     DaemonsetType,\n\t\t\tChildren: []Children{firstPod, secondPod},\n\t\t},\n\t}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveResourceHierarchyWithDgraphError ...\nfunc TestRetrieveResourceHierarchyWithDgraphError(t *testing.T) {\n\tmockDgraphForResourceQueries(testWrongQuery, testDaemonsetName, DaemonsetType)\n\n\tinput := &Resource{\n\t\tCheck:       DaemonsetCheck,\n\t\tType:        DaemonsetType,\n\t\tName:        testDaemonsetName,\n\t\tChildFilter: IsPodFilter,\n\t}\n\tgot := input.RetrieveResourceHierarchy()\n\texpected := JSONDataWrapper{}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveResourceMetricsWithNameEmpty ...\nfunc TestRetrieveResourceMetricsWithNameEmpty(t *testing.T) {\n\tinput := &Resource{\n\t\tCheck: DaemonsetCheck,\n\t\tType:  DaemonsetType,\n\t\tName:  \"\",\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\texpected := JSONDataWrapper{}\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveDaemonsetMetrics ...\nfunc TestRetrieveDaemonsetMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testDaemonsetName, DaemonsetType)\n\n\tinput := &Resource{\n\t\tCheck: DaemonsetCheck,\n\t\tType:  DaemonsetType,\n\t\tName:  testDaemonsetName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testDaemonsetName, DaemonsetType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveDeploymentMetrics ...\nfunc TestRetrieveDeploymentMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testResourceName, DeploymentType)\n\n\tinput := &Resource{\n\t\tCheck: DeploymentCheck,\n\t\tType:  DeploymentType,\n\t\tName:  testResourceName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testResourceName, DeploymentType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveNamespacetMetrics ...\nfunc TestRetrieveNamespacetMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testResourceName, NamespaceType)\n\n\tinput := &Resource{\n\t\tCheck: NamespaceCheck,\n\t\tType:  NamespaceType,\n\t\tName:  testResourceName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testResourceName, NamespaceType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrievePVMetrics ...\nfunc TestRetrievePVMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testResourceName, PVType)\n\n\tinput := &Resource{\n\t\tCheck: PVCheck,\n\t\tType:  PVType,\n\t\tName:  testResourceName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testResourceName, PVType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrievePVCMetrics ...\nfunc TestRetrievePVCMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testResourceName, PVCType)\n\n\tinput := &Resource{\n\t\tCheck: PVCCheck,\n\t\tType:  PVCType,\n\t\tName:  testResourceName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testResourceName, PVCType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveContainerMetrics ...\nfunc TestRetrieveContainerMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testResourceName, ContainerType)\n\n\tinput := &Resource{\n\t\tCheck: ContainerCheck,\n\t\tType:  ContainerType,\n\t\tName:  testResourceName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testResourceName, ContainerType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrieveNodeMetrics ...\nfunc TestRetrieveNodeMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testResourceName, NodeType)\n\n\tinput := &Resource{\n\t\tCheck: NodeCheck,\n\t\tType:  NodeType,\n\t\tName:  testResourceName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testResourceName, NodeType)\n\tassert.Equal(t, expected, got)\n}\n\n// TestRetrievePodMetrics ...\nfunc TestRetrievePodMetrics(t *testing.T) {\n\tmockDgraphForResourceQueries(testMetrics, testPodName, PodType)\n\n\tinput := &Resource{\n\t\tCheck: PodCheck,\n\t\tType:  PodType,\n\t\tName:  testPodName,\n\t}\n\tgot := input.RetrieveResourceMetrics()\n\n\texpected := getExpectedTestMetrics(testPodName, PodType)\n\tassert.Equal(t, expected, got)\n}\n\nfunc getExpectedTestMetrics(name, resourceType string) JSONDataWrapper {\n\tfirstPodWithMetrics := Children{\n\t\tName:        \"pod-purser-1\",\n\t\tType:        PodType,\n\t\tCPU:         0.25,\n\t\tMemory:      0.1,\n\t\tStorage:     1.2,\n\t\tCPUCost:     0.024,\n\t\tMemoryCost:  0.09,\n\t\tStorageCost: 0.1,\n\t}\n\tsecondPodWithMetrics := Children{\n\t\tName:        \"pod-purser-2\",\n\t\tType:        PodType,\n\t\tCPU:         0.15,\n\t\tMemory:      0.2,\n\t\tStorage:     0.2,\n\t\tCPUCost:     0.014,\n\t\tMemoryCost:  0.19,\n\t\tStorageCost: 0.01,\n\t}\n\texpected := JSONDataWrapper{\n\t\tData: ParentWrapper{\n\t\t\tName:        name,\n\t\t\tType:        resourceType,\n\t\t\tChildren:    []Children{firstPodWithMetrics, secondPodWithMetrics},\n\t\t\tCPU:         0.40,\n\t\t\tMemory:      0.28,\n\t\t\tStorage:     1.4,\n\t\t\tCPUCost:     0.038,\n\t\t\tMemoryCost:  0.28,\n\t\t\tStorageCost: 0.11,\n\t\t},\n\t}\n\treturn expected\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/subscriber.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\ntype subscriberRoot struct {\n\tSubscribers []models.SubscriberCRD `json:\"subscribers\"`\n}\n\n// RetrieveSubscribers gets all live subscribers\nfunc RetrieveSubscribers() ([]models.SubscriberCRD, error) {\n\tq := getQueryForSubscribersRetrieval()\n\tnewRoot := subscriberRoot{}\n\terr := executeQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newRoot.Subscribers, nil\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/subscriber_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc mockDgraphForSubscriberQueries(queryType string) {\n\texecuteQuery = func(query string, root interface{}) error {\n\t\tdummySubscriberList, ok := root.(*subscriberRoot)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"wrong root received\")\n\t\t}\n\n\t\tif queryType == testRetrieveSubscribers {\n\t\t\tdummySubscriber := models.SubscriberCRD{\n\t\t\t\tName: \"subscriber-purser\",\n\t\t\t\tSpec: models.SubscriberSpec{\n\t\t\t\t\tURL: \"http://purser.com\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tdummySubscriberList.Subscribers = []models.SubscriberCRD{dummySubscriber}\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"no data found\")\n\t}\n}\n\n// TestRetrieveSubscribersWithDgraphError ...\nfunc TestRetrieveSubscribersWithDgraphError(t *testing.T) {\n\tmockDgraphForSubscriberQueries(testWrongQuery)\n\t_, err := RetrieveSubscribers()\n\tassert.Error(t, err)\n}\n\n// TestRetrieveSubscribers ...\nfunc TestRetrieveSubscribers(t *testing.T) {\n\tmockDgraphForSubscriberQueries(testRetrieveSubscribers)\n\tgot, err := RetrieveSubscribers()\n\texpected := []models.SubscriberCRD{{\n\t\tName: \"subscriber-purser\",\n\t\tSpec: models.SubscriberSpec{\n\t\t\tURL: \"http://purser.com\",\n\t\t},\n\t}}\n\tassert.Equal(t, expected, got)\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/query/types.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage query\n\n// Constants used in query parameters\nconst (\n\tAll      = \"\"\n\tName     = \"name\"\n\tOrphan   = \"orphan\"\n\tView     = \"view\"\n\tPhysical = \"physical\"\n\tLogical  = \"logical\"\n\tFalse    = \"false\"\n)\n\n// Children structure\ntype Children struct {\n\tName        string  `json:\"name,omitempty\"`\n\tType        string  `json:\"type,omitempty\"`\n\tCPU         float64 `json:\"cpu,omitempty\"`\n\tMemory      float64 `json:\"memory,omitempty\"`\n\tStorage     float64 `json:\"storage,omitempty\"`\n\tCPUCost     float64 `json:\"cpuCost,omitempty\"`\n\tMemoryCost  float64 `json:\"memoryCost,omitempty\"`\n\tStorageCost float64 `json:\"storageCost,omitempty\"`\n}\n\n// ParentWrapper structure\ntype ParentWrapper struct {\n\tName             string          `json:\"name,omitempty\"`\n\tType             string          `json:\"type,omitempty\"`\n\tChildren         []Children      `json:\"children,omitempty\"`\n\tParent           []ParentWrapper `json:\"parent,omitempty\"`\n\tCPU              float64         `json:\"cpu,omitempty\"`\n\tMemory           float64         `json:\"memory,omitempty\"`\n\tStorage          float64         `json:\"storage,omitempty\"`\n\tCPUCost          float64         `json:\"cpuCost,omitempty\"`\n\tMemoryCost       float64         `json:\"memoryCost,omitempty\"`\n\tStorageCost      float64         `json:\"storageCost,omitempty\"`\n\tCPUAllocated     float64         `json:\"cpuAllocated,omitempty\"`\n\tMemoryAllocated  float64         `json:\"memoryAllocated,omitempty\"`\n\tStorageAllocated float64         `json:\"storageAllocated,omitempty\"`\n\tCPUCapacity      float64         `json:\"cpuCapacity,omitempty\"`\n\tMemoryCapacity   float64         `json:\"memoryCapacity,omitempty\"`\n\tStorageCapacity  float64         `json:\"storageCapacity,omitempty\"`\n}\n\n// JSONDataWrapper structure\ntype JSONDataWrapper struct {\n\tData ParentWrapper `json:\"data,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/rateCard.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n)\n\n// RateCard constants\nconst (\n\tIsRateCard     = \"isRateCard\"\n\tIsNodePrice    = \"isNodePrice\"\n\tIsStoragePrice = \"isStoragePrice\"\n\tRateCardXID    = \"purser-rateCard\"\n)\n\n// RateCard structure\ntype RateCard struct {\n\tdgraph.ID\n\tIsRateCard    bool            `json:\"isRateCard,omitempty\"`\n\tCloudProvider string          `json:\"cloudProvider,omitempty\"`\n\tRegion        string          `json:\"region,omitempty\"`\n\tNodePrices    []*NodePrice    `json:\"nodePrices,omitempty\"`\n\tStoragePrices []*StoragePrice `json:\"storagePrices,omitempty\"`\n}\n\n// NodePrice structure\n// Unit of Node Price should be USD($)-(per Hour)\ntype NodePrice struct {\n\tdgraph.ID\n\tIsNodePrice     bool    `json:\"isNodePrice,omitempty\"`\n\tInstanceType    string  `json:\"instanceType,omitempty\"`\n\tInstanceFamily  string  `json:\"instanceFamily,omitempty\"`\n\tOperatingSystem string  `json:\"operatingSystem,omitempty\"`\n\tPrice           float64 `json:\"price,omitempty\"`\n\tPricePerCPU     float64 `json:\"cpuPrice,omitempty\"`\n\tPricePerMemory  float64 `json:\"memoryPrice,omitempty\"`\n}\n\n// StoragePrice structure\n// Unit of Storage Price should be USD($)-(per GB)-(per Hour)\ntype StoragePrice struct {\n\tdgraph.ID\n\tIsStoragePrice bool    `json:\"isStoragePrice,omitempty\"`\n\tVolumeType     string  `json:\"volumeType,omitempty\"`\n\tUsageType      string  `json:\"usageType,omitempty\"`\n\tPrice          float64 `json:\"price,omitempty\"`\n}\n\n// StoreRateCard given a cloudProvider and region it gets rate card and stores(create/update) in dgraph\nfunc StoreRateCard(rateCard *RateCard) {\n\tlogrus.Debugf(\"IsRateCardNil: %v\", rateCard == nil)\n\tif rateCard != nil {\n\t\tuid := dgraph.GetUID(RateCardXID, IsRateCard)\n\t\tif uid != \"\" {\n\t\t\trateCard.ID = dgraph.ID{UID: uid, Xid: RateCardXID}\n\t\t}\n\t\tlogrus.Debugf(\"RateCard: (%v)\", rateCard)\n\t\t_, err := dgraph.MutateNode(rateCard, dgraph.CREATE)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"Unable to store rateCard reason: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tlogrus.Infof(\"Successfully stored/updated rateCard\")\n\t}\n}\n\n// StoreNodePrice given nodePrice and its XID it stores(create/update) in dgraph\nfunc StoreNodePrice(nodePrice *NodePrice, productXID string) string {\n\tuid := dgraph.GetUID(productXID, IsNodePrice)\n\tif uid != \"\" {\n\t\tnodePrice.ID = dgraph.ID{Xid: productXID, UID: uid}\n\t}\n\tlogrus.Debugf(\"nodePrice: %v, productXID: %v\\n\", *nodePrice, productXID)\n\tassigned, err := dgraph.MutateNode(nodePrice, dgraph.CREATE)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to store nodePrice: (%v), reason: %v\", nodePrice, err)\n\t\treturn \"\"\n\t}\n\tlogrus.Debugf(\"Successfully stored/updated nodePrice: %v\", productXID)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n\n// StoreStoragePrice given storagePrice and its XID it stores(create/update) in dgraph\nfunc StoreStoragePrice(storagePrice *StoragePrice, productXID string) string {\n\tuid := dgraph.GetUID(productXID, IsStoragePrice)\n\tif uid != \"\" {\n\t\tstoragePrice.ID = dgraph.ID{Xid: productXID, UID: uid}\n\t}\n\tlogrus.Debugf(\"storagePrice: %v, productXID: %v\\n\", *storagePrice, productXID)\n\tassigned, err := dgraph.MutateNode(storagePrice, dgraph.CREATE)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to store storagePrice: (%v), reason: %v\", storagePrice, err)\n\t\treturn \"\"\n\t}\n\tlogrus.Debugf(\"Successfully stored/updated storagePrice: %v\", productXID)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n\n// retrieveNode given a node name it returns pointer to models.Node - nil in case of error\nfunc retrieveNode(name string) (*Node, error) {\n\tquery := `query {\n\t\tnodes(func: has(isNode)) @filter(eq(name, \"` + name + `\")) {\n\t\t\tname\n\t\t\ttype\n\t\t\tstartTime\n\t\t\tendTime\n\t\t\tcpuCapacity\n\t\t\tmemoryCapacity\n\t\t\tinstanceType\n\t\t\tos\n        }\n    }`\n\ttype root struct {\n\t\tNodes []Node `json:\"nodes\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(query, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if len(newRoot.Nodes) < 1 {\n\t\treturn nil, fmt.Errorf(\"no node with name: %v\", name)\n\t}\n\n\treturn &newRoot.Nodes[0], nil\n}\n\n// retrieveNodePrice given a node name it returns pointer to models.Node - nil in case of error\nfunc retrieveNodePrice(xid string) (*NodePrice, error) {\n\tquery := `query {\n\t\tnodePrices(func: has(isNodePrice)) @filter(eq(xid, \"` + xid + `\")) {\n\t\t\tinstanceType\n\t\t\tinstanceFamily\n\t\t\toperatingSystem\n\t\t\tprice\n\t\t\tcpuPrice\n\t\t\tmemoryPrice\n        }\n    }`\n\ttype root struct {\n\t\tNodePrices []NodePrice `json:\"nodePrices\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(query, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if len(newRoot.NodePrices) < 1 {\n\t\treturn nil, fmt.Errorf(\"no node with xid: %v\", xid)\n\t}\n\n\treturn &newRoot.NodePrices[0], nil\n}\n\n// getPerUnitResourcePriceForNode returns price per cpu and price per memory\nfunc getPerUnitResourcePriceForNode(nodeName string) (float64, float64) {\n\tnode, err := retrieveNode(nodeName)\n\tif err == nil {\n\t\treturn getPricePerUnitResourceFromNodePrice(*node)\n\t}\n\treturn DefaultCPUCostInFloat64, DefaultMemCostInFloat64\n}\n\nfunc getPricePerUnitResourceFromNodePrice(node Node) (float64, float64) {\n\tnodePriceXID := node.InstanceType + \"-\" + node.OS\n\tnodePrice, err := retrieveNodePrice(nodePriceXID)\n\tif err == nil {\n\t\treturn nodePrice.PricePerCPU, nodePrice.PricePerMemory\n\t}\n\treturn DefaultCPUCostInFloat64, DefaultMemCostInFloat64\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/replicaset.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\text_v1beta1 \"k8s.io/api/extensions/v1beta1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsReplicaset = \"isReplicaset\"\n)\n\n// Replicaset schema in dgraph\ntype Replicaset struct {\n\tdgraph.ID\n\tIsReplicaset bool        `json:\"isReplicaset,omitempty\"`\n\tName         string      `json:\"name,omitempty\"`\n\tStartTime    string      `json:\"startTime,omitempty\"`\n\tEndTime      string      `json:\"endTime,omitempty\"`\n\tNamespace    *Namespace  `json:\"namespace,omitempty\"`\n\tDeployment   *Deployment `json:\"deployment,omitempty\"`\n\tPods         []*Pod      `json:\"pod,omitempty\"`\n\tType         string      `json:\"type,omitempty\"`\n}\n\nfunc createReplicasetObject(replicaset ext_v1beta1.ReplicaSet) Replicaset {\n\tnewReplicaset := Replicaset{\n\t\tName:         \"replicaset-\" + replicaset.Name,\n\t\tIsReplicaset: true,\n\t\tType:         \"replicaset\",\n\t\tID:           dgraph.ID{Xid: replicaset.Namespace + \":\" + replicaset.Name},\n\t\tStartTime:    replicaset.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(replicaset.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewReplicaset.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: replicaset.Namespace}}\n\t}\n\treplicasetDeletionTimestamp := replicaset.GetDeletionTimestamp()\n\tif !replicasetDeletionTimestamp.IsZero() {\n\t\tnewReplicaset.EndTime = replicasetDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewReplicaset.Xid += newReplicaset.EndTime\n\t\tnewReplicaset.Name += \"*\" + newReplicaset.EndTime\n\t}\n\tsetReplicasetOwners(&newReplicaset, replicaset)\n\treturn newReplicaset\n}\n\n// StoreReplicaset create a new replicaset in the Dgraph and updates if already present.\nfunc StoreReplicaset(replicaset ext_v1beta1.ReplicaSet) (string, error) {\n\txid := replicaset.Namespace + \":\" + replicaset.Name\n\tuid := dgraph.GetUID(xid, IsReplicaset)\n\n\tnewReplicaset := createReplicasetObject(replicaset)\n\tif uid != \"\" {\n\t\tnewReplicaset.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newReplicaset, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\nfunc setReplicasetOwners(r *Replicaset, replicaset ext_v1beta1.ReplicaSet) {\n\towners := replicaset.GetObjectMeta().GetOwnerReferences()\n\tif owners == nil {\n\t\treturn\n\t}\n\tfor _, owner := range owners {\n\t\tif owner.Kind == \"Deployment\" {\n\t\t\tdeploymentXID := replicaset.Namespace + \":\" + owner.Name\n\t\t\tdeploymentUID := CreateOrGetDeploymentByID(deploymentXID)\n\t\t\tif deploymentUID != \"\" {\n\t\t\t\tr.Deployment = &Deployment{ID: dgraph.ID{UID: deploymentUID, Xid: deploymentXID}}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Error(\"Unknown owner type \" + owner.Kind + \" for replicaset.\")\n\t\t}\n\t}\n}\n\n// CreateOrGetReplicasetByID returns the uid of namespace if exists,\n// otherwise creates the replicaset and returns uid.\nfunc CreateOrGetReplicasetByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsReplicaset)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := Replicaset{\n\t\tID:           dgraph.ID{Xid: xid},\n\t\tName:         xid,\n\t\tIsReplicaset: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/service.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/dgraph-io/dgo/protos/api\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsService = \"isService\"\n)\n\n// Service model structure in Dgraph\ntype Service struct {\n\tdgraph.ID\n\tIsService bool       `json:\"isService,omitempty\"`\n\tName      string     `json:\"name,omitempty\"`\n\tStartTime string     `json:\"startTime,omitempty\"`\n\tEndTime   string     `json:\"endTime,omitempty\"`\n\tPod       []*Pod     `json:\"pod,omitempty\"`\n\tInteracts []*Service `json:\"interacts,omitempty\"`\n\tNamespace *Namespace `json:\"namespace,omitempty\"`\n\tType      string     `json:\"type,omitempty\"`\n}\n\nfunc newService(svc api_v1.Service) (*api.Assigned, error) {\n\tnewService := Service{\n\t\tName:      \"service-\" + svc.Name,\n\t\tIsService: true,\n\t\tType:      \"service\",\n\t\tID:        dgraph.ID{Xid: svc.Namespace + \":\" + svc.Name},\n\t\tStartTime: svc.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(svc.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewService.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: svc.Namespace}}\n\t}\n\treturn dgraph.MutateNode(newService, dgraph.CREATE)\n}\n\n// StoreService create a new node in the Dgraph  if it is not present.\nfunc StoreService(service api_v1.Service) error {\n\txid := service.Namespace + \":\" + service.Name\n\tuid := dgraph.GetUID(xid, IsService)\n\n\tif uid == \"\" {\n\t\tassigned, err := newService(service)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.Infof(\"Service with xid: (%s) persisted in dgraph\", xid)\n\t\tuid = assigned.Uids[\"blank-0\"]\n\t}\n\n\tsvcDeletionTimestamp := service.GetDeletionTimestamp()\n\tif !svcDeletionTimestamp.IsZero() {\n\t\tet := svcDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tupdatedService := Service{\n\t\t\tID:      dgraph.ID{Xid: xid + et, UID: uid},\n\t\t\tEndTime: et,\n\t\t\tName:    \"service-\" + service.Name + \"*\" + et,\n\t\t}\n\t\t_, err := dgraph.MutateNode(updatedService, dgraph.UPDATE)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// StoreServicesInteraction stores the service interaction data in the Dgraph\nfunc StoreServicesInteraction(sourceServiceXID string, destinationServicesXIDs []string) error {\n\tuid := dgraph.GetUID(sourceServiceXID, IsService)\n\tif uid == \"\" {\n\t\tlog.Println(\"Source Service \" + sourceServiceXID + \" is not persisted yet.\")\n\t\treturn fmt.Errorf(\"source service: %s is not persisted yet\", sourceServiceXID)\n\t}\n\n\tservices := retrieveServicesFromServicesXIDs(destinationServicesXIDs)\n\tsource := Service{\n\t\tID:        dgraph.ID{UID: uid, Xid: sourceServiceXID},\n\t\tInteracts: services,\n\t}\n\t_, err := dgraph.MutateNode(source, dgraph.UPDATE)\n\treturn err\n}\n\n// StorePodServiceEdges saves pods in Services object in the dgraph\nfunc StorePodServiceEdges(svcXID string, podsXIDsInService []string) error {\n\tsvcUID := dgraph.GetUID(svcXID, IsService)\n\tif svcUID != \"\" {\n\t\tsvcPods := retrievePodsFromPodsXIDs(podsXIDsInService)\n\t\tupdatedService := Service{\n\t\t\tID:  dgraph.ID{UID: svcUID, Xid: svcXID},\n\t\t\tPod: svcPods,\n\t\t}\n\t\t_, err := dgraph.MutateNode(updatedService, dgraph.UPDATE)\n\t\treturn err\n\t}\n\treturn fmt.Errorf(\"service with xid: (%s) not in dgraph\", svcXID)\n}\n\n// RetrieveAllServices returns all pods in the dgraph\nfunc RetrieveAllServices() ([]Service, error) {\n\tconst q = `query {\n\t\tservice(func: has(isService)) {\n\t\t\tname\n\t\t\tinteracts @facets {\n\t\t\t\tname\n\t\t\t}\n\t\t\tpod {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tServices []Service `json:\"service\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newRoot.Services, nil\n}\n\n// RetrieveAllServicesWithDstPods returns all pods in the dgraph\nfunc RetrieveAllServicesWithDstPods() ([]Service, error) {\n\tconst q = `query {\n\t\tservices(func: has(isService)) {\n\t\t\txid\n\t\t\tname\n\t\t\tpod {\n\t\t\t\tname\n\t\t\t\tinteracts @facets {\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tServices []Service `json:\"services\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newRoot.Services, nil\n}\n\n// RetrieveServiceList ...\nfunc RetrieveServiceList() ([]Service, error) {\n\tconst q = `query {\n\t\tserviceList(func: has(isService)) {\n\t\t\tname\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tServiceList []Service `json:\"serviceList\"`\n\t}\n\tnewRoot := root{}\n\terr := dgraph.ExecuteQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newRoot.ServiceList, nil\n}\n\nfunc retrieveServicesFromServicesXIDs(svcsXIDs []string) []*Service {\n\tservices := []*Service{}\n\tfor _, svcXID := range svcsXIDs {\n\t\tsvcUID := dgraph.GetUID(svcXID, IsService)\n\t\tif svcUID == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tservice := &Service{\n\t\t\tID: dgraph.ID{UID: svcUID, Xid: svcXID},\n\t\t}\n\t\tservices = append(services, service)\n\t}\n\treturn services\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/statefulset.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\tapps_v1beta1 \"k8s.io/api/apps/v1beta1\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsStatefulset = \"isStatefulset\"\n)\n\n// Statefulset schema in dgraph\ntype Statefulset struct {\n\tdgraph.ID\n\tIsStatefulset bool       `json:\"isStatefulset,omitempty\"`\n\tName          string     `json:\"name,omitempty\"`\n\tStartTime     string     `json:\"startTime,omitempty\"`\n\tEndTime       string     `json:\"endTime,omitempty\"`\n\tNamespace     *Namespace `json:\"namespace,omitempty\"`\n\tPods          []*Pod     `json:\"pods,omitempty\"`\n\tType          string     `json:\"type,omitempty\"`\n}\n\nfunc createStatefulsetObject(statefulset apps_v1beta1.StatefulSet) Statefulset {\n\tnewStatefulset := Statefulset{\n\t\tName:          \"statefulset-\" + statefulset.Name,\n\t\tIsStatefulset: true,\n\t\tType:          \"statefulset\",\n\t\tID:            dgraph.ID{Xid: statefulset.Namespace + \":\" + statefulset.Name},\n\t\tStartTime:     statefulset.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t}\n\tnamespaceUID := CreateOrGetNamespaceByID(statefulset.Namespace)\n\tif namespaceUID != \"\" {\n\t\tnewStatefulset.Namespace = &Namespace{ID: dgraph.ID{UID: namespaceUID, Xid: statefulset.Namespace}}\n\t}\n\tstatefulsetDeletionTimestamp := statefulset.GetDeletionTimestamp()\n\tif !statefulsetDeletionTimestamp.IsZero() {\n\t\tnewStatefulset.EndTime = statefulsetDeletionTimestamp.Time.Format(time.RFC3339)\n\t\tnewStatefulset.Xid += newStatefulset.EndTime\n\t\tnewStatefulset.Name += \"*\" + newStatefulset.EndTime\n\t}\n\treturn newStatefulset\n}\n\n// StoreStatefulset create a new statefulset in the Dgraph and updates if already present.\nfunc StoreStatefulset(statefulset apps_v1beta1.StatefulSet) (string, error) {\n\txid := statefulset.Namespace + \":\" + statefulset.Name\n\tuid := dgraph.GetUID(xid, IsStatefulset)\n\n\tnewStatefulset := createStatefulsetObject(statefulset)\n\tif uid != \"\" {\n\t\tnewStatefulset.UID = uid\n\t}\n\tassigned, err := dgraph.MutateNode(newStatefulset, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n\n// CreateOrGetStatefulsetByID returns the uid of namespace if exists,\n// otherwise creates the stateful and returns uid.\nfunc CreateOrGetStatefulsetByID(xid string) string {\n\tif xid == \"\" {\n\t\treturn \"\"\n\t}\n\tuid := dgraph.GetUID(xid, IsStatefulset)\n\n\tif uid != \"\" {\n\t\treturn uid\n\t}\n\n\td := Statefulset{\n\t\tID:            dgraph.ID{Xid: xid},\n\t\tName:          xid,\n\t\tIsStatefulset: true,\n\t}\n\tassigned, err := dgraph.MutateNode(d, dgraph.CREATE)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn \"\"\n\t}\n\treturn assigned.Uids[\"blank-0\"]\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/models/subscriber.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage models\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"time\"\n\n\tsubscribers_v1 \"github.com/vmware/purser/pkg/apis/subscriber/v1\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n)\n\n// Dgraph Model Constants\nconst (\n\tIsSubscriber = \"isSubscriber\"\n)\n\n// SubscriberCRD schema in dgraph\ntype SubscriberCRD struct {\n\tdgraph.ID\n\tIsSubscriber bool           `json:\"isSubscriber,omitempty\"`\n\tName         string         `json:\"name,omitempty\"`\n\tStartTime    string         `json:\"startTime,omitempty\"`\n\tEndTime      string         `json:\"endTime,omitempty\"`\n\tType         string         `json:\"type,omitempty\"`\n\tSpec         SubscriberSpec `json:\"spec\"`\n}\n\n// SubscriberSpec definition details\ntype SubscriberSpec struct {\n\tName    string            `json:\"name\"`\n\tHeaders map[string]string `json:\"headers\"`\n\tURL     string            `json:\"url\"`\n}\n\nfunc createSubscriberCRDObject(subscriber subscribers_v1.Subscriber) SubscriberCRD {\n\tnewSubscriber := SubscriberCRD{\n\t\tName:         subscriber.Name,\n\t\tIsSubscriber: true,\n\t\tType:         subscribers_v1.SubscriberGroup,\n\t\tID:           dgraph.ID{Xid: \"subscriber-\" + subscriber.Name},\n\t\tStartTime:    subscriber.GetCreationTimestamp().Time.Format(time.RFC3339),\n\t\tSpec: SubscriberSpec{\n\t\t\tName:    subscriber.Spec.Name,\n\t\t\tHeaders: subscriber.Spec.Headers,\n\t\t\tURL:     subscriber.Spec.URL,\n\t\t},\n\t}\n\n\tdeletionTimestamp := subscriber.GetDeletionTimestamp()\n\tif !deletionTimestamp.IsZero() {\n\t\tnewSubscriber.EndTime = deletionTimestamp.Time.Format(time.RFC3339)\n\t}\n\treturn newSubscriber\n}\n\n// StoreSubscriberCRD create a new subscriber CRD in the Dgraph and updates if already present.\nfunc StoreSubscriberCRD(subscriber subscribers_v1.Subscriber) (string, error) {\n\txid := \"subscriber-\" + subscriber.Name\n\tuid := dgraph.GetUID(xid, IsSubscriber)\n\n\tif uid != \"\" {\n\t\treturn uid, nil\n\t}\n\n\tnewSubscriber := createSubscriberCRDObject(subscriber)\n\tassigned, err := dgraph.MutateNode(newSubscriber, dgraph.CREATE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tlogrus.Infof(\"Subscriber: (%v) persisted in dgraph\", subscriber.Name)\n\treturn assigned.Uids[\"blank-0\"], nil\n}\n"
  },
  {
    "path": "pkg/controller/dgraph/purge.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage dgraph\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"time\"\n)\n\ntype resource struct {\n\tID\n}\n\n// RemoveResourcesInactive deletes all resources which have their deletion time stamp before\n// the start of current month.\nfunc RemoveResourcesInactive() {\n\terr := removeOldDeletedResources()\n\tif err != nil {\n\t\tlog.Println(err)\n\t}\n\n\terr = removeOldDeletedPods()\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n}\n\nfunc removeOldDeletedResources() error {\n\tuids, err := retrieveResourcesWithEndTimeBeforeCurrentMonthStart()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(uids) == 0 {\n\t\tlog.Println(\"No old deleted resources are present in dgraph\")\n\t\treturn nil\n\t}\n\n\t_, err = MutateNode(uids, DELETE)\n\treturn err\n}\n\nfunc removeOldDeletedPods() error {\n\tuids, err := retrievePodsWithEndTimeBeforeThreeMonths()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(uids) == 0 {\n\t\tlog.Println(\"No old deleted pods are present in dgraph\")\n\t\treturn nil\n\t}\n\n\t_, err = MutateNode(uids, DELETE)\n\treturn err\n}\n\nfunc retrieveResourcesWithEndTimeBeforeCurrentMonthStart() ([]resource, error) {\n\tq := `query {\n\t\tresources(func: le(endTime, \"` + utils.ConverTimeToRFC3339(utils.GetCurrentMonthStartTime()) + `\")) @filter(NOT(has(isPod))) {\n\t\t\tuid\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tResources []resource `json:\"resources\"`\n\t}\n\tnewRoot := root{}\n\terr := ExecuteQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newRoot.Resources, nil\n}\n\nfunc retrievePodsWithEndTimeBeforeThreeMonths() ([]resource, error) {\n\tq := `query {\n\t\tresources(func: le(endTime, \"` + utils.ConverTimeToRFC3339(utils.GetCurrentMonthStartTime().Add(-time.Hour*24*30*2)) + `\")) @filter(has(isPod)) {\n\t\t\tuid\n\t\t}\n\t}`\n\n\ttype root struct {\n\t\tResources []resource `json:\"resources\"`\n\t}\n\tnewRoot := root{}\n\terr := ExecuteQuery(q, &newRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newRoot.Resources, nil\n}\n"
  },
  {
    "path": "pkg/controller/discovery/executer/exec.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage executer\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/tools/remotecommand\"\n)\n\n// ExecToPodThroughAPI uninteractively exec to the pod with the command specified.\nfunc ExecToPodThroughAPI(conf controller.Config, pod corev1.Pod, command, containerName string, stdin io.Reader) (string, string, error) {\n\t// Prepare the API URL used to execute another process within the Pod. In this case,\n\t// we'll run a remote shell.\n\treq := conf.Kubeclient.CoreV1().RESTClient().Post().\n\t\tResource(\"pods\").\n\t\tName(pod.Name).\n\t\tNamespace(pod.Namespace).\n\t\tSubResource(\"exec\")\n\n\tscheme := runtime.NewScheme()\n\tif err := corev1.AddToScheme(scheme); err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"error adding to scheme: %v\", err)\n\t}\n\n\tparameterCodec := runtime.NewParameterCodec(scheme)\n\treq.VersionedParams(&corev1.PodExecOptions{\n\t\tCommand:   strings.Fields(command),\n\t\tContainer: containerName,\n\t\tStdin:     stdin != nil,\n\t\tStdout:    true,\n\t\tStderr:    true,\n\t\tTTY:       false,\n\t}, parameterCodec)\n\n\tlog.Debug(\"Request URL:\", req.URL().String())\n\n\texec, err := remotecommand.NewSPDYExecutor(conf.KubeConfig, \"POST\", req.URL())\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"error while creating Executor: %v\", err)\n\t}\n\n\t// Connect this process' std{in,out,err} to the remote shell process.\n\tvar stdout, stderr bytes.Buffer\n\terr = exec.Stream(remotecommand.StreamOptions{\n\t\tStdin:  stdin,\n\t\tStdout: &stdout,\n\t\tStderr: &stderr,\n\t\tTty:    false,\n\t})\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"error in Stream: %v\", err)\n\t}\n\treturn stdout.String(), stderr.String(), nil\n}\n"
  },
  {
    "path": "pkg/controller/discovery/generator/graph.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\n// Node represents each node in the graph\n// ID: unique id of pod\n// Label: pod name\n// Title: string \"pods\"\n// Value: number of times pod has communicated with others\n// Group: Connected component number, used for coloring different components in different colors\n// CID: list of all services the pod belongs to.\ntype Node struct {\n\tID    int      `json:\"id\"`\n\tLabel string   `json:\"label\"`\n\tTitle string   `json:\"title\"`\n\tValue int      `json:\"value\"`\n\tGroup int      `json:\"group\"`\n\tCid   []string `json:\"cid\"`\n}\n\n// Edge represents each edge in the graph\n// From: unique id of source pod\n// TO: unique id of destination pod\n// Title: string containing number of times these two pods communicated\ntype Edge struct {\n\tFrom  int    `json:\"from\"`\n\tTo    int    `json:\"to\"`\n\tTitle string `json:\"title\"`\n}\n\nvar (\n\tuniqueID int\n\tnodes    *[]Node\n\tedges    *[]Edge\n)\n\n// GetGraphNodes returns graph-nodes for pod interactions\nfunc GetGraphNodes() []Node {\n\treturn *nodes\n}\n\n// GetGraphEdges returns graph-edges for pod interactions\nfunc GetGraphEdges() []Edge {\n\treturn *edges\n}\n\n// GeneratePodNodesAndEdges ...\nfunc GeneratePodNodesAndEdges(pods []models.Pod) {\n\tuniqueID = 0\n\tuniqueIDs, numConnections, inboundAndOutboundConnections := getPodUniqueIDsAndNumConnections(pods)\n\tpodNodes := createPodNodes(pods, uniqueIDs, numConnections, inboundAndOutboundConnections)\n\tpodEdges := createPodEdges(pods, uniqueIDs)\n\tsetGraphNodes(podNodes)\n\tsetGraphEdges(podEdges)\n}\n\nfunc getPodUniqueIDsAndNumConnections(pods []models.Pod) (map[string]int, map[string]int, map[string]int) {\n\tuniqueIDs := make(map[string]int)\n\tnumConnections := make(map[string]int)\n\tinboundAndOutboundConnections := make(map[string]int)\n\tfor _, pod := range pods {\n\t\tsetPodUniqueIDsAndNumConnections(pod, uniqueIDs, numConnections, inboundAndOutboundConnections)\n\t}\n\treturn uniqueIDs, numConnections, inboundAndOutboundConnections\n}\n\nfunc setPodUniqueIDsAndNumConnections(pod models.Pod, uniqueIDs, numConnections, inboundAndOutboundConnections map[string]int) {\n\tif _, isPresent := uniqueIDs[pod.Name]; !isPresent {\n\t\tuniqueID++\n\t\tuniqueIDs[pod.Name] = uniqueID\n\t\tnumConnections[pod.Name] = 0\n\t\tfor _, dstPod := range pod.Pods {\n\t\t\tnumConnections[pod.Name] += int(dstPod.Count)\n\t\t\tinboundAndOutboundConnections[pod.Name] += int(dstPod.Count)\n\t\t\tinboundAndOutboundConnections[dstPod.Name] += int(dstPod.Count)\n\t\t}\n\t}\n}\n\nfunc createPodNodes(pods []models.Pod, uniqueIDs, numConnections, inboundAndOutboundConnections map[string]int) []Node {\n\tnodes := []Node{}\n\tduplicateChecker := make(map[string]bool)\n\tfor _, pod := range pods {\n\t\tif _, isNotOrphan := inboundAndOutboundConnections[pod.Name]; isNotOrphan {\n\t\t\tif _, isPresent := duplicateChecker[pod.Name]; !isPresent {\n\t\t\t\tduplicateChecker[pod.Name] = true\n\t\t\t\tsvcCid := []string{}\n\t\t\t\tfor _, svc := range pod.Cid {\n\t\t\t\t\tsvcCid = append(svcCid, svc.Name)\n\t\t\t\t}\n\t\t\t\tnewPodNode := createPodNode(pod.Name, uniqueIDs[pod.Name], numConnections[pod.Name], svcCid)\n\t\t\t\tnodes = append(nodes, newPodNode)\n\t\t\t}\n\t\t}\n\t}\n\treturn nodes\n}\n\nfunc createPodEdges(pods []models.Pod, uniqueIDs map[string]int) []Edge {\n\tedges := []Edge{}\n\tfor _, pod := range pods {\n\t\tsrcID := uniqueIDs[pod.Name]\n\t\tfor _, dstPod := range pod.Pods {\n\t\t\tdestID := uniqueIDs[dstPod.Name]\n\t\t\tedges = append(edges, createPodEdge(srcID, destID, int(dstPod.Count)))\n\t\t}\n\t}\n\treturn edges\n}\n\nfunc createPodNode(podName string, podID int, podConnections int, cid []string) Node {\n\treturn Node{\n\t\tID:    podID,\n\t\tLabel: podName,\n\t\tTitle: \"pods\",\n\t\tValue: podConnections,\n\t\tGroup: 1, // needed for UI, colors different group differently(not needed for our use case)\n\t\tCid:   cid,\n\t}\n}\n\nfunc createPodEdge(fromID int, toID int, count int) Edge {\n\treturn Edge{\n\t\tFrom:  fromID,\n\t\tTo:    toID,\n\t\tTitle: strconv.Itoa(count) + \" times communicated\",\n\t}\n}\n\nfunc setGraphNodes(podNodes []Node) {\n\tnodes = &podNodes\n}\n\nfunc setGraphEdges(podEdges []Edge) {\n\tedges = &podEdges\n}\n"
  },
  {
    "path": "pkg/controller/discovery/linker/podlinks.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage linker\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\n// InteractionsWrapper ...\ntype InteractionsWrapper struct {\n\tPodInteractions             map[string](map[string]float64)\n\tProcessToPodInteraction     map[string](map[string]bool)\n\tContainerProcessInteraction map[string][]string\n}\n\n// podIPTable: maps pod name with pod IP address\n// podToPodTable: maps src pod to the interacting dest pod along with the interaction frequency count.\nvar (\n\tpodIPTable    = make(map[string]string)\n\tpodToPodTable = make(map[string](map[string]float64))\n)\n\nvar (\n\tmu sync.Mutex\n)\n\nconst (\n\t// KeySpliter splits the key into resource namespace and name used for processing Xids\n\tKeySpliter = \":\"\n)\n\n// PopulatePodIPTable populates the podIP<->podName map\nfunc PopulatePodIPTable(pods *corev1.PodList) {\n\tfor _, pod := range pods.Items {\n\t\tpodIP := pod.Status.PodIP\n\t\tpodIPTable[podIP] = pod.Namespace + KeySpliter + pod.Name\n\t}\n}\n\n// GenerateAndStorePodInteractions generates source to destination Pod mapping and stores it in Dgraph.\nfunc GenerateAndStorePodInteractions() {\n\tlog.Info(\"Storing Pod Interactions ....\")\n\tfor srcPodName, communication := range podToPodTable {\n\t\tdstPods := []string{}\n\t\tcounts := []float64{}\n\t\tfor dstPodName, count := range communication {\n\t\t\tdstPods = append(dstPods, dstPodName)\n\t\t\tcounts = append(counts, count)\n\t\t}\n\t\terr := models.StorePodsInteraction(srcPodName, dstPods, counts)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"failed to store pod interaction in Dgraph %v\", err)\n\t\t}\n\t}\n\tlog.Info(\"Finished storing pod interactions.\")\n}\n\n// PopulateMappingTables updates PodToPodTable\nfunc PopulateMappingTables(tcpDump []string, pod corev1.Pod, process Process, containerName string, interactions *InteractionsWrapper) {\n\tpodXID := pod.Namespace + KeySpliter + pod.Name\n\tcontainerXID := podXID + KeySpliter + containerName\n\tprocXID := containerXID + KeySpliter + process.ID + KeySpliter + process.Name\n\tpopulateContainerProcessTable(containerXID, procXID, interactions)\n\tfor _, address := range tcpDump {\n\t\taddress := strings.Split(address, KeySpliter)\n\t\tsrcIP, dstIP := address[0], address[2]\n\t\tsrcName, dstName := podIPTable[srcIP], podIPTable[dstIP]\n\t\tupdatePodInteractions(srcName, dstName, interactions)\n\t\tupdatePodProcessInteractions(procXID, dstName, interactions)\n\t}\n}\n\nfunc updatePodInteractions(srcName, dstName string, interactions *InteractionsWrapper) {\n\tif dstName != \"\" && srcName != \"\" {\n\t\tlog.Debugf(\"pod interactions srcName: (%s), dstName: (%s)\", srcName, dstName)\n\t\tif _, ok := interactions.PodInteractions[srcName]; !ok {\n\t\t\tinteractions.PodInteractions[srcName] = make(map[string]float64)\n\t\t}\n\n\t\tif _, isPresent := interactions.PodInteractions[srcName][dstName]; !isPresent {\n\t\t\tinteractions.PodInteractions[srcName][dstName] = 1\n\t\t} else {\n\t\t\tinteractions.PodInteractions[srcName][dstName]++\n\t\t}\n\t}\n}\n\n// UpdatePodToPodTable ...\nfunc UpdatePodToPodTable(podInteractions map[string](map[string]float64)) {\n\tmu.Lock()\n\tfor srcPod, interaction := range podInteractions {\n\t\tif _, ok := podToPodTable[srcPod]; !ok {\n\t\t\tpodToPodTable[srcPod] = interaction\n\t\t} else {\n\t\t\tfor dstPod, count := range interaction {\n\t\t\t\tif _, isPresent := podToPodTable[srcPod][dstPod]; !isPresent {\n\t\t\t\t\tpodToPodTable[srcPod][dstPod] = count\n\t\t\t\t} else {\n\t\t\t\t\tpodToPodTable[srcPod][dstPod] += count\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tmu.Unlock()\n}\n"
  },
  {
    "path": "pkg/controller/discovery/linker/processlinks.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage linker\n\nimport (\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\n// Process holds the details for the executing processes inside the container\ntype Process struct {\n\tID, Name string\n}\n\n// StoreProcessInteractions stores process, container to process edge, process to pods edge\nfunc StoreProcessInteractions(containerProcessInteraction map[string][]string, processPodInteraction map[string](map[string]bool), creationTime time.Time) {\n\tfor containerXID, procsXIDs := range containerProcessInteraction {\n\t\tfor _, procXID := range procsXIDs {\n\t\t\tpodsXIDs := []string{}\n\t\t\tfor podXID := range processPodInteraction[procXID] {\n\t\t\t\tpodsXIDs = append(podsXIDs, podXID)\n\t\t\t}\n\n\t\t\terr := models.StoreProcess(procXID, containerXID, podsXIDs, creationTime)\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"failed to store process details: %s, err: (%v)\", procXID, err)\n\t\t\t}\n\t\t}\n\t\terr := models.StoreContainerProcessEdge(containerXID, procsXIDs)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"failed to store edge from container: %s to procs, err: (%v)\", containerXID, err)\n\t\t}\n\t}\n}\n\nfunc populateContainerProcessTable(containerXID, procXID string, interactions *InteractionsWrapper) {\n\tif _, isPresent := interactions.ContainerProcessInteraction[containerXID]; !isPresent {\n\t\tinteractions.ContainerProcessInteraction[containerXID] = []string{}\n\t}\n\tinteractions.ContainerProcessInteraction[containerXID] = append(interactions.ContainerProcessInteraction[containerXID], procXID)\n}\n\nfunc updatePodProcessInteractions(procXID, dstName string, interactions *InteractionsWrapper) {\n\tif dstName != \"\" {\n\t\tif _, isPresent := interactions.ProcessToPodInteraction[procXID]; !isPresent {\n\t\t\tinteractions.ProcessToPodInteraction[procXID] = make(map[string]bool)\n\t\t}\n\t\tinteractions.ProcessToPodInteraction[procXID][dstName] = true\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/discovery/linker/servicelinks.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage linker\n\nimport (\n\t\"sync\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nvar (\n\tpodToSvcTable = make(map[string][]string)\n\tserviceMu     sync.Mutex\n)\n\n// PopulatePodToServiceTable populates the pod<->service map\nfunc PopulatePodToServiceTable(svc corev1.Service, pods *corev1.PodList) {\n\tvar podsXIDsInService []string\n\tserviceKey := svc.Namespace + KeySpliter + svc.Name\n\n\tserviceMu.Lock()\n\tfor _, pod := range pods.Items {\n\t\tpodKey := pod.Namespace + KeySpliter + pod.Name\n\t\tpodToSvcTable[podKey] = append(podToSvcTable[podKey], serviceKey)\n\t\tpodsXIDsInService = append(podsXIDsInService, podKey)\n\t}\n\tserviceMu.Unlock()\n\n\terr := models.StorePodServiceEdges(serviceKey, podsXIDsInService)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to store pod services edges: %s\\n\", err)\n\t}\n}\n\n// GenerateAndStoreSvcInteractions parses through pod interactions and generates a source to // destination service interaction.\nfunc GenerateAndStoreSvcInteractions() {\n\tservices, err := models.RetrieveAllServicesWithDstPods()\n\tif err != nil {\n\t\tlog.Errorf(\"failed to fetch services: %s\\n\", err)\n\t}\n\n\tfor _, service := range services {\n\t\tdestinationPods := getDestinationPods(service.Pod)\n\t\tdestinationServices := getServicesXIDsFromPods(destinationPods)\n\t\terr = models.StoreServicesInteraction(service.Xid, destinationServices)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"failed to store services interactions: %s\\n\", err)\n\t\t}\n\t}\n}\n\nfunc getDestinationPods(podsInService []*models.Pod) []*models.Pod {\n\tvar destinationPods []*models.Pod\n\tfor _, pod := range podsInService {\n\t\tdestinationPods = append(destinationPods, pod.Pods...)\n\t}\n\treturn destinationPods\n}\n\nfunc getServicesXIDsFromPods(pods []*models.Pod) []string {\n\tvar servicesXIDs []string\n\tduplicateChecker := make(map[string]bool)\n\tfor _, pod := range pods {\n\t\tsvcsXIDs := podToSvcTable[pod.Xid]\n\t\tfor _, svcXID := range svcsXIDs {\n\t\t\tif _, isPresent := duplicateChecker[svcXID]; !isPresent {\n\t\t\t\tduplicateChecker[svcXID] = true\n\t\t\t\tservicesXIDs = append(servicesXIDs, svcXID)\n\t\t\t}\n\t\t}\n\t}\n\treturn servicesXIDs\n}\n"
  },
  {
    "path": "pkg/controller/discovery/processor/container.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage processor\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/vmware/purser/pkg/controller\"\n\t\"github.com/vmware/purser/pkg/controller/discovery/linker\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/discovery/executer\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nfunc processContainerDetails(conf controller.Config, pod corev1.Pod, containers []corev1.Container) linker.InteractionsWrapper {\n\tinteractions := linker.InteractionsWrapper{\n\t\tPodInteractions:             make(map[string](map[string]float64)),\n\t\tProcessToPodInteraction:     make(map[string](map[string]bool)),\n\t\tContainerProcessInteraction: make(map[string][]string),\n\t}\n\tfor _, container := range containers {\n\t\tpidList, cmdList := getPIDList(conf, pod, container.Name)\n\t\tfor index, pid := range pidList {\n\t\t\tprocess := linker.Process{ID: pid, Name: cmdList[index]}\n\t\t\tgetProcessDump(conf, pod, container.Name, process, &interactions)\n\t\t}\n\t}\n\treturn interactions\n}\n\nfunc getPIDList(conf controller.Config, pod corev1.Pod, containerName string) ([]string, []string) {\n\tcommand := \"ps -A -o pid,cmd\"\n\toutput, err := executeCommandInPod(conf, pod, command, containerName)\n\tif err != nil {\n\t\treturn nil, nil\n\t}\n\n\tpidCMDList := strings.Split(output, \"\\n\")\n\n\tvar pidList, cmdList []string\n\tfor _, pidCMD := range pidCMDList {\n\t\tif pidCMD != \"\" {\n\t\t\tpidCMDClean := strings.Split((strings.TrimSpace(pidCMD)), \" \")\n\t\t\tpidList = append(pidList, pidCMDClean[0])\n\t\t\tcmdList = append(cmdList, strings.Join(pidCMDClean[1:], \"-\"))\n\t\t}\n\t}\n\t// ignore first line i.e, PID CMD headers\n\tif len(pidList) >= 1 {\n\t\treturn pidList[1:], cmdList[1:]\n\t}\n\treturn pidList, cmdList\n}\n\nfunc getProcessDump(conf controller.Config, pod corev1.Pod, containerName string, process linker.Process, interactions *linker.InteractionsWrapper) {\n\t//get tcp information from /proc/pid/net/tcp for each process\n\tif process.ID != \"\" {\n\t\ttcpCommand := \"cat /proc/\" + process.ID + \"/net/tcp\"\n\t\ttcpOutput, err := executeCommandInPod(conf, pod, tcpCommand, containerName)\n\t\tif err == nil {\n\t\t\t//to clean dump only to have required fields\n\t\t\ttcpDump := utils.PurgeTCPData(tcpOutput)\n\t\t\tlinker.PopulateMappingTables(tcpDump, pod, process, containerName, interactions)\n\t\t}\n\n\t\ttcp6Command := \"cat /proc/\" + process.ID + \"/net/tcp6\"\n\t\ttcp6Output, err := executeCommandInPod(conf, pod, tcp6Command, containerName)\n\t\tif err == nil {\n\t\t\t//to clean dump only to have required fields\n\t\t\ttcp6Dump := utils.PurgeTCP6Data(tcp6Output)\n\t\t\tlinker.PopulateMappingTables(tcp6Dump, pod, process, containerName, interactions)\n\t\t}\n\t}\n}\n\nfunc executeCommandInPod(conf controller.Config, pod corev1.Pod, command, containerName string) (string, error) {\n\toutput, stderr, err := executer.ExecToPodThroughAPI(conf, pod, command, containerName, nil)\n\n\tif err != nil {\n\t\tlog.Debugf(\"Failed `exec`ing to the container %q, command %q Error: %+v\", pod.Name, command, err)\n\t}\n\n\tif len(stderr) > 0 {\n\t\tlog.Warnf(\"stderr: %v\", stderr)\n\t\terr = fmt.Errorf(\"stderr: %v\", stderr)\n\t}\n\n\treturn output, err\n}\n"
  },
  {
    "path": "pkg/controller/discovery/processor/pod.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage processor\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\t\"sync\"\n\n\t\"github.com/vmware/purser/pkg/controller\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/discovery/linker\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst maxGoRoutines = 20\n\nvar wg sync.WaitGroup\n\n// ProcessPodInteractions fetches details of all the running processes in each container of\n// each pod in a given namespace and generates a 1:1 mapping between the communicating pods.\nfunc ProcessPodInteractions(conf controller.Config) {\n\tk8sPods := utils.RetrievePodList(conf.Kubeclient, metav1.ListOptions{})\n\tif k8sPods == nil {\n\t\tlog.Info(\"No pods retrieved from cluster\")\n\t\treturn\n\t}\n\n\tlinker.PopulatePodIPTable(k8sPods)\n\tprocessPodDetails(conf, k8sPods)\n\n\tlinker.GenerateAndStorePodInteractions()\n\tlog.Infof(\"Successfully generated Pod To Pod mapping.\")\n}\n\nfunc processPodDetails(conf controller.Config, pods *corev1.PodList) {\n\tpodsCount := len(pods.Items)\n\tlog.Infof(\"Processing total of (%d) Pods.\", podsCount)\n\n\tfreeRoutines := maxGoRoutines\n\tnumChannelsReceived := 0\n\tch := make(chan int, 1)\n\n\twg.Add(podsCount)\n\t{\n\t\tfor index, pod := range pods.Items {\n\t\t\tlog.Debugf(\"Processing Pod: (%s), (%d/%d) ... \", pod.Name, index+1, podsCount)\n\n\t\t\t// wait for a free goroutine\n\t\t\tif freeRoutines < 1 {\n\t\t\t\t// wait for a go routine to send to channel i.e, it will wait until a go routine finishes.\n\t\t\t\t<-ch\n\t\t\t\tnumChannelsReceived++\n\t\t\t}\n\n\t\t\t// decrease 1 from freeRoutines before starting a new go routine\n\t\t\tfreeRoutines--\n\t\t\tgo func(pod corev1.Pod, index int) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tcontainers := pod.Spec.Containers\n\t\t\t\tinteractions := processContainerDetails(conf, pod, containers)\n\t\t\t\tlinker.UpdatePodToPodTable(interactions.PodInteractions)\n\t\t\t\tlinker.StoreProcessInteractions(interactions.ContainerProcessInteraction, interactions.ProcessToPodInteraction,\n\t\t\t\t\tpod.GetCreationTimestamp().Time)\n\t\t\t\tlog.Debugf(\"Finished processing Pod: (%s), (%d/%d)\", pod.Name, index+1, podsCount)\n\n\t\t\t\t// increase 1 from freeRoutines after processing a pod.\n\t\t\t\tfreeRoutines++\n\t\t\t\t// send 1 to channel\n\t\t\t\tch <- 1\n\t\t\t}(pod, index)\n\t\t}\n\t}\n\t// receive channel from remaining go routines\n\tfor i := 0; i < podsCount-numChannelsReceived; i++ {\n\t\t<-ch\n\t}\n\twg.Wait()\n\tclose(ch)\n}\n"
  },
  {
    "path": "pkg/controller/discovery/processor/svc.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage processor\n\nimport (\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\t\"sync\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\t\"github.com/vmware/purser/pkg/controller\"\n\t\"github.com/vmware/purser/pkg/controller/discovery/linker\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nvar svcwg sync.WaitGroup\n\n// ProcessServiceInteractions parses through the list of services and it's associated pods to\n// generate a 1:1 mapping between the communicating services.\nfunc ProcessServiceInteractions(conf controller.Config) {\n\tservices := utils.RetrieveServiceList(conf.Kubeclient, metav1.ListOptions{})\n\tif services == nil {\n\t\tlog.Info(\"No services retrieved from cluster\")\n\t\treturn\n\t}\n\n\tprocessServiceDetails(conf.Kubeclient, services)\n\tlinker.GenerateAndStoreSvcInteractions()\n\n\tlog.Infof(\"Successfully generated Service To Service mapping.\")\n}\n\nfunc processServiceDetails(client *kubernetes.Clientset, services *corev1.ServiceList) {\n\tsvcCount := len(services.Items)\n\tlog.Infof(\"Processing total of (%d) Services.\", svcCount)\n\n\tsvcwg.Add(svcCount)\n\t{\n\t\tfor index, svc := range services.Items {\n\t\t\tlog.Debugf(\"Processing Service (%d/%d): %s \", index+1, svcCount, svc.GetName())\n\n\t\t\tgo func(svc corev1.Service, index int) {\n\t\t\t\tdefer svcwg.Done()\n\n\t\t\t\tselectorSet := labels.Set(svc.Spec.Selector)\n\t\t\t\tif selectorSet != nil {\n\t\t\t\t\toptions := metav1.ListOptions{\n\t\t\t\t\t\tLabelSelector: selectorSet.AsSelector().String(),\n\t\t\t\t\t}\n\t\t\t\t\tpods := utils.RetrievePodList(client, options)\n\t\t\t\t\tif pods != nil {\n\t\t\t\t\t\tlinker.PopulatePodToServiceTable(svc, pods)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlog.Debugf(\"Finished processing Service (%d/%d)\", index+1, svcCount)\n\t\t\t}(svc, index)\n\t\t}\n\t}\n\tsvcwg.Wait()\n}\n"
  },
  {
    "path": "pkg/controller/eventprocessor/notifier.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage eventprocessor\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\t\"net/http\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller\"\n)\n\n// ReadSize defines the default payload read size\nconst ReadSize uint32 = 50\n\ntype notifier struct {\n\turl     string\n\theaders map[string]string\n}\n\nfunc notifySubscribers(payload []*interface{}, subscribers []models.SubscriberCRD) {\n\tnotifiers := getNotifiers(subscribers)\n\n\tfor _, n := range notifiers {\n\t\treq, err := n.createNewRequest(payload)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Failed to unmarshal payload and create new request %v\", err)\n\t\t} else {\n\t\t\terr := retry(3, time.Second, func() error {\n\t\t\t\treturn sendData(req)\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"Notification to subscriber %v failed after 3 retries %v\", n.url, err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (n notifier) createNewRequest(payload []*interface{}) (*http.Request, error) {\n\tpayloadWrapper := controller.PayloadWrapper{Data: payload}\n\n\tjsonStr, err := json.Marshal(payloadWrapper)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling payload %v\", err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", n.url, bytes.NewBuffer(jsonStr))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating HTTP request %v\", err)\n\t}\n\tn.setReqHeaders(req)\n\treturn req, nil\n}\n\nfunc sendData(req *http.Request) error {\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error sending data to %v: %v\", req.URL, err)\n\t}\n\n\tif resp != nil {\n\t\tif resp.StatusCode != 200 {\n\t\t\treturn fmt.Errorf(\"payload data posting failed for %v, %s\", req.URL, resp.Status)\n\t\t}\n\t\tlog.Debugf(\"Payload data posted successfully for %v\", req.URL)\n\t}\n\treturn nil\n}\n\nfunc (n *notifier) setReqHeaders(r *http.Request) {\n\tr.Header.Set(\"Content-Type\", \"application/json\")\n\tfor key, value := range n.headers {\n\t\tr.Header.Set(key, value)\n\t}\n}\n\nfunc getNotifiers(subscribers []models.SubscriberCRD) []*notifier {\n\tvar notifiers []*notifier\n\tif len(subscribers) > 0 {\n\t\tfor _, sub := range subscribers {\n\t\t\tnotifier := &notifier{\n\t\t\t\turl:     sub.Spec.URL,\n\t\t\t\theaders: sub.Spec.Headers,\n\t\t\t}\n\t\t\tnotifiers = append(notifiers, notifier)\n\t\t}\n\t} else {\n\t\tlog.Debug(\"No subscribers available.\")\n\t}\n\treturn notifiers\n}\n\nfunc retry(attempts int, sleep time.Duration, fn func() error) error {\n\tif err := fn(); err != nil {\n\t\tif attempts--; attempts > 0 {\n\t\t\ttime.Sleep(sleep)\n\t\t\treturn retry(attempts, 2*sleep, fn)\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/eventprocessor/processor.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage eventprocessor\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\tsubcriber_v1 \"github.com/vmware/purser/pkg/apis/subscriber/v1\"\n\t\"github.com/vmware/purser/pkg/controller\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\tapps_v1beta1 \"k8s.io/api/apps/v1beta1\"\n\tbatch_v1 \"k8s.io/api/batch/v1\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n\text_v1beta1 \"k8s.io/api/extensions/v1beta1\"\n)\n\n// ProcessEvents processes the event and notifies the subscribers.\nfunc ProcessEvents(conf *controller.Config) {\n\n\tfor {\n\t\tconf.RingBuffer.PrintDetails()\n\n\t\tfor {\n\t\t\tdata, size := conf.RingBuffer.ReadN(ReadSize)\n\n\t\t\tif size == 0 {\n\t\t\t\tlog.Debug(\"No new events to process.\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tProcessPayloads(data, conf)\n\n\t\t\tsubscribers, err := query.RetrieveSubscribers()\n\t\t\tif err == nil {\n\t\t\t\tnotifySubscribers(data, subscribers)\n\t\t\t} else {\n\t\t\t\tlog.Errorf(\"unable to retrieve subscribers from dgraph: %v\", err)\n\t\t\t}\n\n\t\t\tconf.RingBuffer.RemoveN(size)\n\t\t\tconf.RingBuffer.PrintDetails()\n\t\t}\n\t\ttime.Sleep(10 * time.Second)\n\t}\n}\n\n// ProcessPayloads store payload info in dgraph. If payload is of type group then it updates its group spec\nfunc ProcessPayloads(payloads []*interface{}, conf *controller.Config) {\n\tfor _, event := range payloads {\n\t\tpayload := (*event).(*controller.Payload)\n\t\thandlePayloadBasedOnResource(payload, conf)\n\t}\n}\n\n// nolint: gocyclo\nfunc handlePayloadBasedOnResource(payload *controller.Payload, conf *controller.Config) {\n\tvar err error\n\tswitch payload.ResourceType {\n\tcase \"Pod\":\n\t\tpod := api_v1.Pod{}\n\t\tunmarshalPayload(payload, &pod)\n\t\terr = models.StorePod(pod)\n\tcase \"Service\":\n\t\tservice := api_v1.Service{}\n\t\tunmarshalPayload(payload, &service)\n\t\terr = models.StoreService(service)\n\tcase \"Node\":\n\t\tnode := api_v1.Node{}\n\t\tunmarshalPayload(payload, &node)\n\t\t_, err = models.StoreNode(node)\n\tcase \"Namespace\":\n\t\tns := api_v1.Namespace{}\n\t\tunmarshalPayload(payload, &ns)\n\t\t_, err = models.StoreNamespace(ns)\n\tcase \"Deployment\":\n\t\tdeployment := apps_v1beta1.Deployment{}\n\t\tunmarshalPayload(payload, &deployment)\n\t\t_, err = models.StoreDeployment(deployment)\n\tcase \"ReplicaSet\":\n\t\treplicaset := ext_v1beta1.ReplicaSet{}\n\t\tunmarshalPayload(payload, &replicaset)\n\t\t_, err = models.StoreReplicaset(replicaset)\n\tcase \"StatefulSet\":\n\t\tstatefulset := apps_v1beta1.StatefulSet{}\n\t\tunmarshalPayload(payload, &statefulset)\n\t\t_, err = models.StoreStatefulset(statefulset)\n\tcase \"PersistentVolume\":\n\t\tpv := api_v1.PersistentVolume{}\n\t\tunmarshalPayload(payload, &pv)\n\t\t_, err = models.StorePersistentVolume(pv, conf.Kubeclient)\n\tcase \"PersistentVolumeClaim\":\n\t\tpvc := api_v1.PersistentVolumeClaim{}\n\t\tunmarshalPayload(payload, &pvc)\n\t\t_, err = models.StorePersistentVolumeClaim(pvc)\n\tcase \"DaemonSet\":\n\t\tdaemonset := ext_v1beta1.DaemonSet{}\n\t\tunmarshalPayload(payload, &daemonset)\n\t\t_, err = models.StoreDaemonset(daemonset)\n\tcase \"Job\":\n\t\tjob := batch_v1.Job{}\n\t\tunmarshalPayload(payload, &job)\n\t\t_, err = models.StoreJob(job)\n\tcase \"Group\":\n\t\tgroupCRD := &groups_v1.Group{}\n\t\tunmarshalPayload(payload, &groupCRD)\n\t\thandlePayloadForGroup(payload, conf, groupCRD.Name)\n\tcase \"Subscriber\":\n\t\tsubscriberCRD := subcriber_v1.Subscriber{}\n\t\tunmarshalPayload(payload, &subscriberCRD)\n\t\t_, err = models.StoreSubscriberCRD(subscriberCRD)\n\t}\n\tcheckDgraphError(payload.ResourceType, err)\n}\n\nfunc unmarshalPayload(payload *controller.Payload, resource interface{}) {\n\terr := json.Unmarshal([]byte(payload.Data), resource)\n\tif err != nil {\n\t\tlog.Errorf(\"Error un marshalling payload \" + payload.Data)\n\t}\n}\n\nfunc checkDgraphError(resource string, err error) {\n\tif err != nil {\n\t\tlog.Errorf(\"Error while persisting %s %v\", resource, err)\n\t}\n}\n\nfunc handlePayloadForGroup(payload *controller.Payload, conf *controller.Config, groupName string) {\n\tif payload.EventType == controller.Delete {\n\t\tmodels.DeleteGroup(groupName)\n\t} else {\n\t\tgroup, err := conf.Groupcrdclient.Get(groupName)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Unable to get group from client: (%v)\", err)\n\t\t} else {\n\t\t\tUpdateGroup(group, conf.Groupcrdclient)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/eventprocessor/sync.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage eventprocessor\n\nimport (\n\t\"time\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// SyncCluster will handle missed events\nfunc SyncCluster(kubeClient *kubernetes.Clientset) {\n\tendTime := time.Now().Format(time.RFC3339)\n\tsyncPods(kubeClient, endTime)\n}\n\n// syncPods handles missed creation and deletion of pod events\nfunc syncPods(kubeClient *kubernetes.Clientset, endTime string) {\n\tlogrus.Infof(\"[SYNC] started syncing pods\")\n\tlivePodsFromDgraph := query.RetrieveAllLivePods()\n\tlogrus.Infof(\"[SYNC] number of livePodsFromDgraph: %d\", len(livePodsFromDgraph))\n\n\tpodsInCluster := utils.RetrievePodList(kubeClient, v1.ListOptions{})\n\tif podsInCluster == nil {\n\t\tlogrus.Errorf(\"[SYNC] got no podsInCluster, aborting sync\")\n\t\treturn\n\t}\n\tlogrus.Infof(\"[SYNC] number of pods in cluster: %d\", len(podsInCluster.Items))\n\n\thandleDeadPodsAndNewPods(livePodsFromDgraph, podsInCluster, endTime)\n\tlogrus.Infof(\"[SYNC] finished syncing of pods\")\n}\n\n// if dead pods end time isn't updated in dgraph this function will update it\n// if an pod creation event is missed then this function will create a new pod in dgraph\nfunc handleDeadPodsAndNewPods(livePodsFromDgraph []models.Pod, podsInCluster *corev1.PodList, endTime string) {\n\t// create a map from pod xid to k8s pod pointer\n\tpodXIDToPod := make(map[string]*corev1.Pod)\n\tfor _, pod := range podsInCluster.Items {\n\t\txid := pod.Namespace + \":\" + pod.Name\n\t\tif _, isPresent := podXIDToPod[xid]; !isPresent {\n\t\t\tpodXIDToPod[xid] = &pod\n\t\t}\n\t}\n\n\tvar deadPods []models.Pod\n\tpodsXIDs := make(map[string]bool) // create a map from pod xid to bool\n\tfor _, pod := range livePodsFromDgraph {\n\t\tif _, isAlive := podXIDToPod[pod.Xid]; !isAlive {\n\t\t\t// pod is in dgraph but not in cluster -> pod got deleted but end time not updated in dgraph ->\n\t\t\t// missed pod deletion event -> update pod in dgraph with end time\n\t\t\tdeadPod := models.Pod{\n\t\t\t\tID:      dgraph.ID{Xid: pod.Xid + endTime, UID: pod.UID},\n\t\t\t\tEndTime: endTime,\n\t\t\t\tName:    \"pod-\" + pod.Name + \"*\" + endTime,\n\t\t\t}\n\t\t\tdeadPods = append(deadPods, deadPod)\n\t\t}\n\n\t\tif _, isPresent := podsXIDs[pod.Xid]; !isPresent {\n\t\t\tpodsXIDs[pod.Xid] = true\n\t\t}\n\t}\n\n\t// update deletion time stamps for dead pods\n\t_, err := dgraph.MutateNode(deadPods, dgraph.UPDATE)\n\tif err != nil {\n\t\tlogrus.Errorf(\"[SYNC] unable to update deleted pods with end time: # deleted pods: %d, err: %v\", len(deadPods), err)\n\t}\n\n\t// create new pod if it isn't in dgraph\n\tfor podXID, pod := range podXIDToPod {\n\t\tif _, isPresent := podsXIDs[podXID]; !isPresent {\n\t\t\t// pod is in cluster but not in dgraph -> missed pod creation event -> create new pod in dgraph\n\t\t\terr = models.StorePod(*pod)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.Errorf(\"[SYNC] Error while persisting pod: %s, err: %v\", podXID, err)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/eventprocessor/updater.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage eventprocessor\n\nimport (\n\t\"time\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models/query\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\tgroupsClient_v1 \"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// UpdateGroups retrieve all groups and updates them\nfunc UpdateGroups(groupCRDClient *groupsClient_v1.GroupClient) {\n\tlog.Infof(\"Started updating groups\")\n\tgroups := utils.RetrieveGroupList(groupCRDClient, meta_v1.ListOptions{})\n\tif groups == nil {\n\t\tlog.Debugf(\"GroupList is nil\")\n\t\treturn\n\t}\n\tlog.Debugf(\"Retrieved groups of length: %d\", len(groups.Items))\n\tfor _, group := range groups.Items {\n\t\tUpdateGroup(group, groupCRDClient)\n\t}\n}\n\n// UpdateGroup given a group it updates its spec with metrics\nfunc UpdateGroup(group *groups_v1.Group, groupCRDClient *groupsClient_v1.GroupClient) {\n\tif group == nil {\n\t\tlog.Warn(\"Received empty group to update\")\n\t\treturn\n\t}\n\n\tgroupMetrics := getGroupMetrics(group)\n\tlog.Debugf(\"GroupMetrics computed from dgraph data: (%v)\", groupMetrics)\n\tgroup.Spec.MTDMetrics = &groups_v1.GroupMetrics{\n\t\tCPURequest:    groupMetrics.MTDCpu,\n\t\tMemoryRequest: groupMetrics.MTDMemory,\n\t\tStorageClaim:  groupMetrics.MTDStorage,\n\t}\n\tgroup.Spec.PITMetrics = &groups_v1.GroupMetrics{\n\t\tCPURequest:    groupMetrics.PITCpu,\n\t\tMemoryRequest: groupMetrics.PITMemory,\n\t\tStorageClaim:  groupMetrics.PITStorage,\n\t}\n\tgroup.Spec.MTDCost = &groups_v1.Cost{\n\t\tCPUCost:     groupMetrics.CostCPU,\n\t\tMemoryCost:  groupMetrics.CostMemory,\n\t\tStorageCost: groupMetrics.CostStorage,\n\t\tTotalCost:   groupMetrics.CostCPU + groupMetrics.CostMemory + groupMetrics.CostStorage,\n\t}\n\tgroup.Spec.PerHourCost = &groups_v1.Cost{\n\t\tCPUCost:     groupMetrics.CostCPUPerHour,\n\t\tMemoryCost:  groupMetrics.CostMemoryPerHour,\n\t\tStorageCost: groupMetrics.CostStoragePerHour,\n\t\tTotalCost:   groupMetrics.CostCPUPerHour + groupMetrics.CostMemoryPerHour + groupMetrics.CostStoragePerHour,\n\t}\n\tgroup.Spec.LastMonthCost = &groups_v1.Cost{\n\t\tCPUCost:     groupMetrics.LastMonthCPUCost,\n\t\tMemoryCost:  groupMetrics.LastMonthMemoryCost,\n\t\tStorageCost: groupMetrics.LastMonthStorageCost,\n\t\tTotalCost:   groupMetrics.LastMonthCPUCost + groupMetrics.LastMonthMemoryCost + groupMetrics.LastMonthStorageCost,\n\t}\n\tgroup.Spec.LastLastMonthCost = &groups_v1.Cost{\n\t\tCPUCost:     groupMetrics.LastLastMonthCPUCost,\n\t\tMemoryCost:  groupMetrics.LastLastMonthMemoryCost,\n\t\tStorageCost: groupMetrics.LastLastMonthStorageCost,\n\t\tTotalCost:   groupMetrics.LastLastMonthCPUCost + groupMetrics.LastLastMonthMemoryCost + groupMetrics.LastLastMonthStorageCost,\n\t}\n\tgroup.Spec.LastUpdated = time.Now()\n\n\t_, err := groupCRDClient.Update(group)\n\tif err != nil {\n\t\tlog.Errorf(\"unable to update group: (%s), error: (%v)\", group.Name, err)\n\t\treturn\n\t}\n\n\tlog.Debugf(\"Updated group spec: (%v)\", group.Spec)\n\tlog.Infof(\"Group spec is updated with metrics for group: (%s)\", group.Name)\n\t_, err = models.CreateOrUpdateGroup(group, groupMetrics.PodsCount)\n\tif err != nil {\n\t\tlog.Errorf(\"unable to create or update group in dgraph: (%s), error: (%v)\", group.Name, err)\n\t}\n}\n\nfunc getGroupMetrics(group *groups_v1.Group) query.GroupMetrics {\n\tlog.Debugf(\"Group: (%v), expressions: (%v)\", group.Name, group.Spec.Expressions)\n\n\t// for each label-expression retrieve UIDs of pods that satisfy the label-expression\n\tpodUIDsFromExpressions := getPodUIDsFromAllExpressions(group.Spec.Expressions)\n\tlog.Debugf(\"Group: (%v), pod uids: (%v)\", group.Name, podUIDsFromExpressions)\n\n\t// Across all the podUIDs computed from label-expressions, map pod's UID with number of occurrences of it\n\tpodUIDsCounter := mapPodUIDsToNumberOfOccurences(podUIDsFromExpressions)\n\tlog.Debugf(\"Group: (%v), pod uids counter: (%v)\", group.Name, podUIDsCounter)\n\n\t// if number of occurrences of UID == number of expressions that means the pod satisfies all the expressions(i.e, AND)\n\t// get uid-query to retrieve such pods i.e, \"uid1, uid2, uid2...\"\n\tuidQueryForPods := getUIDQueryForPods(podUIDsCounter, len(group.Spec.Expressions))\n\tlog.Debugf(\"Group: (%v), uidQuery: (%v)\", group.Name, uidQueryForPods)\n\n\t// get group metrics\n\tgroupMetrics, err := query.RetrieveGroupMetricsFromPodUIDs(uidQueryForPods)\n\tif err != nil {\n\t\tlog.Errorf(\"Unable to retrieve group metrics, group: %v, UIDs: (%v)\", group.Name, uidQueryForPods)\n\t\treturn query.GroupMetrics{}\n\t}\n\treturn groupMetrics\n}\n\n// for each label-expression retrieve UIDs of pods that satisfy the label-expression\n// appends results from each expression and returns the array of such results i.e,\n// [[pod1-from-exp1, pod2-from-exp1], [pod1-from-exp2], [pod1-from-exp3, pod2-from-exp3, pod3-from-exp3]]\nfunc getPodUIDsFromAllExpressions(expressions map[string]map[string][]string) [][]string {\n\tvar podsUIDsFromExpressions [][]string\n\tfor _, selector := range expressions {\n\t\tlabelFilter := query.CreateFilterFromListOfLabels(selector)\n\t\tpodsUIDsFromSelector, err := query.RetrievePodsUIDsByLabelsFilter(labelFilter)\n\t\tif err == nil {\n\t\t\tpodsUIDsFromExpressions = append(podsUIDsFromExpressions, podsUIDsFromSelector)\n\t\t}\n\t}\n\treturn podsUIDsFromExpressions\n}\n\n// across all the podUIDs computed from label-expressions, map pod's UID with number of occurrences of it and return map\nfunc mapPodUIDsToNumberOfOccurences(podsFromExpressions [][]string) map[string]int {\n\tpodsUIDsCounter := make(map[string]int)\n\tfor _, podsFromExpression := range podsFromExpressions {\n\t\tfor _, pod := range podsFromExpression {\n\t\t\tif _, isPresent := podsUIDsCounter[pod]; !isPresent {\n\t\t\t\tpodsUIDsCounter[pod] = 0\n\t\t\t}\n\t\t\tpodsUIDsCounter[pod]++\n\t\t}\n\t}\n\treturn podsUIDsCounter\n}\n\n// returns UIDs for pods that satisfy (number of its occurrences == expressions count) i.e,\n// if number of occurrences of UID == number of expressions that means the pod satisfies all the expressions(-> AND)\n// returns uid-query(i.e, \"uid1, uid2, uid2...\") that can retrieve desired pods\nfunc getUIDQueryForPods(podsUIDsCounter map[string]int, expressionsCount int) string {\n\tseparator := \", \"\n\tisFirst := true\n\tvar uidQueryForPods string\n\tfor podUID, count := range podsUIDsCounter {\n\t\tif count == expressionsCount {\n\t\t\tif !isFirst {\n\t\t\t\tuidQueryForPods += separator\n\t\t\t} else {\n\t\t\t\tisFirst = false\n\t\t\t}\n\t\t\tuidQueryForPods += podUID\n\t\t}\n\t}\n\treturn uidQueryForPods\n}\n"
  },
  {
    "path": "pkg/controller/metrics/metrics.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage metrics\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n)\n\n// Metrics types\ntype Metrics struct {\n\tCPULimit      *resource.Quantity\n\tMemoryLimit   *resource.Quantity\n\tCPURequest    *resource.Quantity\n\tMemoryRequest *resource.Quantity\n}\n\n// CalculatePodStatsFromContainers returns the cumulative metrics from the containers.\nfunc CalculatePodStatsFromContainers(pod *api_v1.Pod) *Metrics {\n\tcpuLimit := &resource.Quantity{}\n\tmemoryLimit := &resource.Quantity{}\n\tcpuRequest := &resource.Quantity{}\n\tmemoryRequest := &resource.Quantity{}\n\tfor _, c := range pod.Spec.Containers {\n\t\tlimits := c.Resources.Limits\n\t\tif limits != nil {\n\t\t\tcpuLimit.Add(*limits.Cpu())\n\t\t\tmemoryLimit.Add(*limits.Memory())\n\t\t}\n\n\t\trequests := c.Resources.Requests\n\t\tif requests != nil {\n\t\t\tcpuRequest.Add(*requests.Cpu())\n\t\t\tmemoryRequest.Add(*requests.Memory())\n\t\t}\n\t}\n\treturn &Metrics{\n\t\tCPULimit:      cpuLimit,\n\t\tMemoryLimit:   memoryLimit,\n\t\tCPURequest:    cpuRequest,\n\t\tMemoryRequest: memoryRequest,\n\t}\n}\n\n// PrintPodStats displays the pod stats.\nfunc PrintPodStats(pod *api_v1.Pod, metrics *Metrics) {\n\tlog.Printf(\"Pod:\\t%s\\n\", pod.Name)\n\tlog.Printf(\"\\tCPU Limit = %s\\n\", metrics.CPULimit.String())\n\tlog.Printf(\"\\tMemory Limit = %s\\n\", metrics.MemoryLimit.String())\n\tlog.Printf(\"\\tCPU Request = %s\\n\", metrics.CPURequest.String())\n\tlog.Printf(\"\\tMemory Request = %s\\n\", metrics.MemoryRequest.String())\n}\n"
  },
  {
    "path": "pkg/controller/payload.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport meta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n// PayloadWrapper holds additional information about payload\ntype PayloadWrapper struct {\n\tData []*interface{} `json:\"data\"`\n}\n\n// Payload holds payload information\ntype Payload struct {\n\tKey          string `json:\"key\"`\n\tEventType    string `json:\"eventType\"`\n\tResourceType string `json:\"resourceType\"`\n\tCloudType    string `json:\"cloudType\"`\n\tData         string `json:\"data\"`\n\tCaptureTime  meta_v1.Time\n}\n"
  },
  {
    "path": "pkg/controller/persistentVolume.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n\tapi_v1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// UpdatePodVolumeClaims activePodVolumeClaims: current active(bounded) podVolumeClaims for the pod.\n// old podVolumeClaims: Claims(map) for the pod before this update.\n// compares old podVolumeClaims and activePodVolumeClaims. This function hadles 3 cases.\n// Case Unbound pvc:\n//\t\tPresent as 'bounded' in old podVolumeClaims. Not present in activePodVolumeClaims.\n// Case New PVC:\n//\t\tNot present in old podVolumeClaims. Present in activePodVolumeClaims.\n// Case Bound an unbounded pvc:\n//\t\tPresent as 'unbounded' in old podVolumeClaims. Present in activePodVolumeClaims.\nfunc UpdatePodVolumeClaims(pod api_v1.Pod, podDetails groups_v1.PodDetails, eventTime meta_v1.Time) groups_v1.PodDetails {\n\tactivePodVolumeClaims := getactivePodVolumeClaims(pod)\n\n\tpodVolumeClaims := podDetails.PodVolumeClaims\n\tif podVolumeClaims == nil {\n\t\tpodVolumeClaims = map[string]*groups_v1.PersistentVolumeClaim{}\n\t}\n\n\tfor claimName := range podVolumeClaims {\n\t\t// isPresent: true if pvc is present in activePodVolumeClaims.\n\t\t_, isPresent := activePodVolumeClaims[claimName]\n\n\t\t// isBounded: true if pvc is present in old podVolumeClaims as 'bounded'\n\t\tisBounded := checkBounded(podVolumeClaims[claimName])\n\n\t\tif (!isPresent) && isBounded {\n\t\t\t// Case Unbound pvc\n\t\t\tlog.Info(\"Unbounded pvc: \" + claimName + \" from pod: \" + podDetails.Name)\n\t\t\tpodVolumeClaims[claimName].UnboundTimes = append(podVolumeClaims[claimName].UnboundTimes, eventTime)\n\t\t} else if isPresent && (!isBounded) {\n\t\t\t// Case Bound an unbounded pvc\n\t\t\tlog.Info(\"Bounded pvc: \" + claimName + \" to pod: \" + podDetails.Name)\n\t\t\tpodVolumeClaims[claimName].BoundTimes = append(podVolumeClaims[claimName].BoundTimes, eventTime)\n\t\t}\n\t}\n\n\t// check for new pvc\n\tfor claimKey := range activePodVolumeClaims {\n\t\t_, isPresent := podVolumeClaims[claimKey]\n\t\tif !isPresent {\n\t\t\t// Case New PVC\n\t\t\tlog.Info(\"Bounded new pvc: \" + claimKey + \"to pod: \" + podDetails.Name)\n\t\t\tpodVolumeClaims[claimKey] = activePodVolumeClaims[claimKey]\n\t\t}\n\t}\n\n\t// TODO: handle Case Resizing of PVC\n\n\tpodDetails.PodVolumeClaims = podVolumeClaims\n\treturn podDetails\n}\n\nfunc getactivePodVolumeClaims(pod api_v1.Pod) map[string]*groups_v1.PersistentVolumeClaim {\n\tnamespace := pod.GetNamespace()\n\tpodVolumeClaims := map[string]*groups_v1.PersistentVolumeClaim{}\n\tfor j := 0; j < len(pod.Spec.Volumes); j++ {\n\t\tvol := pod.Spec.Volumes[j]\n\t\tif vol.PersistentVolumeClaim != nil {\n\t\t\tclaimName := vol.PersistentVolumeClaim.ClaimName\n\t\t\tpodVolumeClaims[claimName] = collectPersistentVolumeClaim(claimName, namespace)\n\t\t\tpodVolumeClaims[claimName].BoundTimes = append(podVolumeClaims[claimName].BoundTimes, pod.GetCreationTimestamp())\n\t\t}\n\t}\n\treturn podVolumeClaims\n}\n\nfunc collectPersistentVolumeClaim(claimName, namespace string) *groups_v1.PersistentVolumeClaim {\n\tpvc, err := Kubeclient.CoreV1().PersistentVolumeClaims(namespace).Get(claimName, meta_v1.GetOptions{})\n\tif isPVCError(err, claimName) {\n\t\treturn nil\n\t}\n\n\trequest := pvc.Spec.Resources.Requests[\"storage\"].DeepCopy()\n\tcapacity := pvc.Status.Capacity[\"storage\"].DeepCopy()\n\n\treturn &groups_v1.PersistentVolumeClaim{\n\t\tName:                  pvc.GetObjectMeta().GetName(),\n\t\tVolumeName:            pvc.Spec.VolumeName,\n\t\tRequestSizeInGB:       []float64{utils.BytesToGB(request.Value())},\n\t\tCapacityAllocatedInGB: []float64{utils.BytesToGB(capacity.Value())},\n\t\tBoundTimes:            []meta_v1.Time{},\n\t\tUnboundTimes:          []meta_v1.Time{},\n\t}\n}\n\n// PvcHandlePodDeletion action to be taken when pod is deleted.\n// Unbound all bounded pvcs.\nfunc PvcHandlePodDeletion(podDetails *groups_v1.PodDetails) {\n\tpvMap := podDetails.PodVolumeClaims\n\tfor claimName := range pvMap {\n\t\tif checkBounded(pvMap[claimName]) {\n\t\t\tlog.Info(\"Unbounded pvc: \" + claimName + \" Reason deletion of pod: \" + podDetails.Name)\n\t\t\tpvMap[claimName].UnboundTimes = append(pvMap[claimName].UnboundTimes, podDetails.EndTime)\n\t\t}\n\t}\n\tpodDetails.PodVolumeClaims = pvMap\n}\n\n// If length of bound times is 1 more than unbound times it means that the pvc is still bound to pod.\nfunc checkBounded(pvc *groups_v1.PersistentVolumeClaim) bool {\n\treturn len(pvc.BoundTimes)-len(pvc.UnboundTimes) == 1\n}\n\n// false if no error\nfunc isPVCError(err error, claimName string) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\tif errors.IsNotFound(err) {\n\t\tlog.Errorf(\"Persistent Volume Claim %s not found\\n\", claimName)\n\t\treturn true\n\t}\n\tif statusError, isStatus := err.(*errors.StatusError); isStatus {\n\t\tlog.Errorf(\"Error getting persistence volume Claim %s : %v\\n\", claimName, statusError.ErrStatus.Message)\n\t\treturn true\n\t}\n\tlog.Errorf(\"Error: Unable to get PVC: %s\\n\", claimName)\n\treturn true\n}\n"
  },
  {
    "path": "pkg/controller/types.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport (\n\tgroups_v1 \"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n\tsubscriber_v1 \"github.com/vmware/purser/pkg/client/clientset/typed/subscriber/v1\"\n\t\"github.com/vmware/purser/pkg/controller/buffering\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// These are the event types supported for controllers\nconst (\n\tCreate = \"create\"\n\tDelete = \"delete\"\n\tUpdate = \"update\"\n)\n\n// Resource contains resource configuration\ntype Resource struct {\n\tPod                   bool `json:\"po\"`\n\tNode                  bool `json:\"node\"`\n\tPersistentVolume      bool `json:\"pv\"`\n\tPersistentVolumeClaim bool `json:\"pvc\"`\n\tService               bool `json:\"service\"`\n\tReplicaSet            bool `json:\"replicaset\"`\n\tStatefulSet           bool `json:\"statefulset\"`\n\tDeployment            bool `json:\"deployment\"`\n\tJob                   bool `json:\"job\"`\n\tDaemonSet             bool `json:\"daemonset\"`\n\tNamespace             bool `json:\"namespace\"`\n\tGroup                 bool `json:\"groups.vmware.purser.com\"`\n\tSubscriber            bool `json:\"subscribers.vmware.purser.com\"`\n}\n\n// Config contains config objects\ntype Config struct {\n\tKubeConfig       *rest.Config\n\tResource         Resource `json:\"resource\"`\n\tRingBuffer       *buffering.RingBuffer\n\tGroupcrdclient   *groups_v1.GroupClient\n\tSubscriberclient *subscriber_v1.SubscriberClient\n\tKubeclient       *kubernetes.Clientset\n}\n"
  },
  {
    "path": "pkg/controller/utils/jsonutils.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\n// JSONMarshal marshal object and returns in byte. If there is an error then it return nil.\nfunc JSONMarshal(obj interface{}) []byte {\n\tbytes, err := json.Marshal(obj)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\treturn bytes\n}\n\n// GetJSONResponse retrieves json response and converts it to target object.\n// Returns error if any failure is encountered.\nfunc GetJSONResponse(client *http.Client, url string, target interface{}) error {\n\tresp, err := client.Get(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer closeResponse(resp)\n\n\treturn json.NewDecoder(resp.Body).Decode(target)\n}\n\nfunc closeResponse(resp *http.Response) {\n\terr := resp.Body.Close()\n\tif err != nil {\n\t\tlog.Errorf(\"unable to close response body. Reason: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/utils/k8sUtils.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\tstoragev1 \"k8s.io/api/storage/v1\"\n\n\tgroupsv1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\tgroups \"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n\n\tapi_v1 \"k8s.io/api/core/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// k8sUtils constants\nconst (\n\tStorageDefault = \"purser-default\"\n)\n\n// RetrievePodList returns list of pods in the given namespace.\nfunc RetrievePodList(client *kubernetes.Clientset, options metav1.ListOptions) *corev1.PodList {\n\tpods, err := client.CoreV1().Pods(metav1.NamespaceAll).List(options)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to retrieve pods: %v\", err)\n\t\treturn nil\n\t}\n\treturn pods\n}\n\n// RetrieveNodeList returns list of nodes\nfunc RetrieveNodeList(client *kubernetes.Clientset, options metav1.ListOptions) *corev1.NodeList {\n\tnodes, err := client.CoreV1().Nodes().List(options)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to retrieve nodes: %v\", err)\n\t\treturn nil\n\t}\n\treturn nodes\n}\n\n// RetrieveServiceList returns list of services in the given namespace.\nfunc RetrieveServiceList(client *kubernetes.Clientset, options metav1.ListOptions) *corev1.ServiceList {\n\tservices, err := client.CoreV1().Services(metav1.NamespaceAll).List(options)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to retrieve services: %v\", err)\n\t\treturn nil\n\t}\n\treturn services\n}\n\n// RetrieveGroupList returns list of group CRDs in the given namespace.\nfunc RetrieveGroupList(groupClient *groups.GroupClient, options metav1.ListOptions) *groupsv1.GroupList {\n\tcrdGroups, err := groupClient.List(options)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to retrieve group list: %v \", err)\n\t\treturn nil\n\t}\n\treturn crdGroups\n}\n\n// RetrieveStorageClass returns storage class with the given name. Nil if error is encountered\nfunc RetrieveStorageClass(client *kubernetes.Clientset, options metav1.GetOptions, name string) (*storagev1.StorageClass, error) {\n\tstorageClass, err := client.StorageV1().StorageClasses().Get(name, options)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to retrieve storage class: %s, err: %v\", name, err)\n\t\treturn nil, err\n\t}\n\treturn storageClass, err\n}\n\n// GetFinalStorageTypeOfPV ...\n// input: persistent volume\n// output: the type(final) of PV's storage class\n// i.e., if PV has storage class A, A is of type B(storage class) and so on..\n// until a storage class X is of its own type X. Then this function returns the final type of PV's storage as X\n//\n// \"purser-default\" is returned in special cases:\n// 1. if A is of type B, if B is of type A (i.e., if a cycle is found)\n// 2. an error is encountered\n// 3. if A is not having any type i.e., \"\" (empty string case)\nfunc GetFinalStorageTypeOfPV(pv api_v1.PersistentVolume, client *kubernetes.Clientset) string {\n\tcycleChecker := make(map[string]bool)\n\tlog.Debugf(\"PV: %s, storageClass: %s\", pv.Name, pv.Spec.StorageClassName)\n\treturn getFinalTypeOfStorageClass(client, pv.Spec.StorageClassName, cycleChecker)\n}\n\n// getFinalTypeOfStorageClass\n// this is helper function for func getStorageType\nfunc getFinalTypeOfStorageClass(client *kubernetes.Clientset, storageClassName string, cycleChecker map[string]bool) string {\n\tif _, isVisited := cycleChecker[storageClassName]; isVisited {\n\t\treturn StorageDefault\n\t}\n\tcycleChecker[storageClassName] = true\n\n\tstorageClass, err := RetrieveStorageClass(client, metav1.GetOptions{}, storageClassName)\n\tif err != nil {\n\t\treturn StorageDefault\n\t}\n\n\tstorageType := storageClass.Parameters[\"type\"]\n\tif storageType == \"\" {\n\t\treturn StorageDefault\n\t} else if storageType == storageClassName {\n\t\treturn storageClassName\n\t}\n\treturn getFinalTypeOfStorageClass(client, storageType, cycleChecker)\n}\n"
  },
  {
    "path": "pkg/controller/utils/purge.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/Sirupsen/logrus\"\n)\n\n// PurgeTCPData handles IP conversion from Hex to Dec and cleans up data to contain only\n// inter pod address information.\nfunc PurgeTCPData(data string) []string {\n\tvar tcpDump []string\n\n\ttcpDumpHex := getTCPDumpHexFromData(data)\n\tfor _, address := range tcpDumpHex {\n\t\tlocalIP, localPort := hexToDecIP(address[6:14]), address[15:19]\n\t\tremoteIP, remotePort := hexToDecIP(address[20:28]), address[29:33]\n\n\t\tif isLocalHost(localIP, remoteIP) {\n\t\t\tcontinue\n\t\t}\n\n\t\taddressMapping := localIP + \":\" + localPort + \":\" + remoteIP + \":\" + remotePort\n\t\ttcpDump = append(tcpDump, addressMapping)\n\t}\n\treturn tcpDump\n}\n\n// PurgeTCP6Data handles IP conversion from Hex to Dec and cleans up data to contain only\n// inter pod address information.\nfunc PurgeTCP6Data(data string) []string {\n\tvar tcpDump []string\n\n\ttcpDumpHex := getTCPDumpHexFromData(data)\n\tfor _, address := range tcpDumpHex {\n\t\tlocalIP, localPort := hexToDecIP(address[30:38]), address[39:43]\n\t\tremoteIP, remotePort := hexToDecIP(address[68:76]), address[77:81]\n\n\t\tif isLocalHost(localIP, remoteIP) {\n\t\t\tcontinue\n\t\t}\n\n\t\taddressMapping := localIP + \":\" + localPort + \":\" + remoteIP + \":\" + remotePort\n\t\ttcpDump = append(tcpDump, addressMapping)\n\t}\n\treturn tcpDump\n}\n\nfunc getTCPDumpHexFromData(data string) []string {\n\ttcpDumpHex := strings.Split(data, \"\\n\")\n\tif len(tcpDumpHex) <= 1 {\n\t\treturn nil\n\t}\n\n\t// ignore title and last one as it is empty\n\ttcpDumpHex = tcpDumpHex[1 : len(tcpDumpHex)-1]\n\treturn tcpDumpHex\n}\n\nfunc hexToDecIP(hexIP string) string {\n\tdecBytes, err := hex.DecodeString(hexIP)\n\tif err != nil {\n\t\tlogrus.Warnf(\"failed to decode string to hex %v\", err)\n\t}\n\treturn fmt.Sprintf(\"%v.%v.%v.%v\", decBytes[3], decBytes[2], decBytes[1], decBytes[0])\n}\n\nfunc isLocalHost(localIP, remoteIP string) bool {\n\treturn strings.Compare(localIP, \"0.0.0.0\") == 0 || strings.Compare(localIP, \"127.0.0.1\") == 0 || strings.Compare(remoteIP, \"0.0.0.0\") == 0\n}\n"
  },
  {
    "path": "pkg/controller/utils/purge_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/vmware/purser/test/utils\"\n)\n\nfunc TestHexToDecIP(t *testing.T) {\n\tact := hexToDecIP(\"030310AC\")\n\texp := \"172.16.3.3\"\n\tutils.Equals(t, exp, act)\n}\n"
  },
  {
    "path": "pkg/controller/utils/timeUtils.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport \"time\"\n\n// GetCurrentMonthStartTime returns month start time as k8s apimachinery Time object\nfunc GetCurrentMonthStartTime() time.Time {\n\tnow := time.Now()\n\tmonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)\n\treturn monthStart\n}\n\n// ConverTimeToRFC3339 returns query time in RFC3339 format\nfunc ConverTimeToRFC3339(queryTime time.Time) string {\n\treturn queryTime.Format(time.RFC3339)\n}\n\n// GetSecondsSince returns number of seconds since query time\nfunc GetSecondsSince(queryTime time.Time) float64 {\n\treturn time.Since(queryTime).Seconds()\n}\n\n// GetHoursRemainingInCurrentMonth returns number of hours remaining in the month\nfunc GetHoursRemainingInCurrentMonth() float64 {\n\tnow := time.Now()\n\tmonthEnd := time.Date(now.Year(), now.Month(), 30, 23, 59, 0, 0, time.Local)\n\treturn -time.Since(monthEnd).Hours()\n}\n"
  },
  {
    "path": "pkg/controller/utils/unitConversions.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"strconv\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n)\n\n// BytesToGB converts from bytes(int64) to GB(float64)\nfunc BytesToGB(val int64) float64 {\n\treturn float64BytesToFloat64GB(float64(val))\n}\n\n// ConvertToFloat64GB quantity to float64 GB\nfunc ConvertToFloat64GB(quantity *resource.Quantity) float64 {\n\treturn float64BytesToFloat64GB(resourceToFloat64(quantity))\n}\n\n// ConvertToFloat64CPU quantity to float64 vCPU\nfunc ConvertToFloat64CPU(quantity *resource.Quantity) float64 {\n\treturn resourceToFloat64(quantity)\n}\n\n// AddResourceAToResourceB ...\nfunc AddResourceAToResourceB(resA, resB *resource.Quantity) {\n\tif resA != nil {\n\t\tresB.Add(*resA)\n\t}\n}\n\n// float64BytesToFloat64GB from bytes (float64) to GB(float64)\nfunc float64BytesToFloat64GB(val float64) float64 {\n\treturn val / (1024.0 * 1024.0 * 1024.0)\n}\n\n// resourceToFloat64 ...\nfunc resourceToFloat64(quantity *resource.Quantity) float64 {\n\tdecVal := quantity.AsDec()\n\tdecValueFloat, err := strconv.ParseFloat(decVal.String(), 64)\n\tif err != nil {\n\t\tlog.Errorf(\"error while converting into string: (%s) to float\\n\", decVal.String())\n\t}\n\treturn decValueFloat // 0 if not isSuccess\n}\n"
  },
  {
    "path": "pkg/controller/utils/unitConversions_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/vmware/purser/test/utils\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n)\n\nfunc TestBytesToGB(t *testing.T) {\n\tact := BytesToGB(124235312345978)\n\texp := 115703.15095221438\n\tutils.Equals(t, exp, act)\n}\n\nfunc TestConvertToFloat64GB(t *testing.T) {\n\tquantities := getTestQuantities()\n\texp := [3]float64{0.011175870895385742, 0.01171875, 0.011175870895385742}\n\tfor index, quantity := range quantities {\n\t\tact := ConvertToFloat64GB(&quantity)\n\t\tutils.Equals(t, exp[index], act)\n\t}\n}\n\nfunc TestConvertToFloat64CPU(t *testing.T) {\n\tquantities := getTestQuantities()\n\texp := [3]float64{1.2e+07, 1.2582912e+07, 1.2e+07}\n\tfor index, quantity := range quantities {\n\t\tact := ConvertToFloat64CPU(&quantity)\n\t\tutils.Equals(t, exp[index], act)\n\t}\n}\n\nfunc getTestQuantities() [3]resource.Quantity {\n\tvar quantities [3]resource.Quantity\n\tquantities[0], _ = resource.ParseQuantity(\"12e6\")\n\tquantities[1], _ = resource.ParseQuantity(\"12Mi\")\n\tquantities[2], _ = resource.ParseQuantity(\"12M\")\n\n\treturn quantities\n}\n"
  },
  {
    "path": "pkg/plugin/costing.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/vmware/purser/pkg/plugin/metrics\"\n\t\"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// Cost details\ntype Cost struct {\n\tTotalCost   float64\n\tCPUCost     float64\n\tMemoryCost  float64\n\tStorageCost float64\n}\n\n// ClientSetInstance helps in accessing kubernetes apis through client.\nvar ClientSetInstance *kubernetes.Clientset\n\n// ProvideClientSetInstance sets the client set instance.\nfunc ProvideClientSetInstance(clientset *kubernetes.Clientset) {\n\tClientSetInstance = clientset\n}\n\n// GetPodsCostForLabel returns pods cost for given label.\nfunc GetPodsCostForLabel(label string) {\n\tpods := getPodsForLabelThroughClient(label)\n\tpods = getPodsCost(pods)\n\tprintPodsVerbose(pods)\n}\n\n// GetClusterSummary summarizes cluster metrics.\nfunc GetClusterSummary() {\n\tpods := GetClusterPods()\n\tpodMetrics := metrics.CalculatePodStatsFromContainers(pods)\n\tfmt.Println(\"==============================\")\n\tfmt.Printf(\"Cluster Summary\\n\")\n\tfmt.Println(\"==============================\")\n\n\tfmt.Println()\n\tfmt.Println(\"\\tCompute:\")\n\tnodes := GetClusterNodes()\n\tfmt.Printf(\"\\t\\t%s\\t\\t\\t%d\\n\", \"Node count:\", len(nodes))\n\n\tnodeMetrics := metrics.CalculateNodeStats(nodes)\n\tfmt.Printf(\"\\t\\tTotal Capacity:\\n\")\n\tfmt.Printf(\"\\t\\t\\t%s\\t\\t%d\\n\", \"CPU(vCPU):\", nodeMetrics.CPULimit.Value())\n\n\tfmt.Printf(\"\\t\\t\\t%s\\t\\t%.2f\\n\", \"Memory(GB):\", bytesToGB(nodeMetrics.MemoryLimit.Value()))\n\n\tfmt.Printf(\"\\t\\tProvisioned Resources:\\n\")\n\tfmt.Printf(\"\\t\\t\\t%s\\t%d\\n\", \"CPU Request(vCPU):\", podMetrics.CPURequest.Value())\n\tfmt.Printf(\"\\t\\t\\t%s\\t%.2f\\n\", \"Memory Request(GB):\", bytesToGB(podMetrics.MemoryRequest.Value()))\n\n\tprice := GetUserCosts()\n\thoursInMonthTillNow := totalHoursTillNow()\n\n\tcpuCost := float64(nodeMetrics.CPULimit.Value()) * hoursInMonthTillNow * price.CPU\n\tmemCost := bytesToGB(nodeMetrics.MemoryLimit.Value()) * hoursInMonthTillNow * price.Memory\n\tcomputeCost := cpuCost + memCost\n\n\tfmt.Println()\n\tfmt.Printf(\"\\tStorage:\\n\")\n\n\tpvs := GetClusterVolumes()\n\tstorageCost, storageCapacity := getPvCostAndCapacity(pvs)\n\n\tfmt.Printf(\"\\t\\t%s\\t%d\\n\", \"Persistent Volume count:\", len(pvs))\n\tfmt.Printf(\"\\t\\t%s\\t\\t\\t%.2f\\n\", \"Capacity(GB):\", bytesToGB(storageCapacity))\n\n\tpvcs := GetClusterPersistentVolumeClaims()\n\t_, pvcCapacity := getPvcCostAndCapacity(pvcs)\n\tfmt.Printf(\"\\t\\t%s\\t\\t\\t%d\\n\", \"PV Claim count:\", len(pvcs))\n\n\tfmt.Printf(\"\\t\\t%s\\t\\t%.2f\\n\", \"PV Claim Capacity(GB):\", bytesToGB(pvcCapacity))\n\n\tfmt.Println()\n\tfmt.Printf(\"\\tMonth To Date Cost:\\n\")\n\tfmt.Printf(\"\\t\\t%s\\t\\t%.2f\\n\", \"Compute cost($):\", computeCost)\n\tfmt.Printf(\"\\t\\t%s\\t\\t%.2f\\n\", \"Storage cost($):\", storageCost)\n\tfmt.Printf(\"\\t\\t%s\\t\\t\\t%.2f\\n\", \"Total cost($):\", computeCost+storageCost)\n}\n\n// GetSavings returns the savings summary.\nfunc GetSavings() {\n\tfmt.Printf(\"Savings Summary\\n\")\n\n\tfmt.Printf(\"Storage:\\n\")\n\tpvs := GetClusterVolumes()\n\tstorageCost, storageCapacity := getPvCostAndCapacity(pvs)\n\n\tpvcs := GetClusterPersistentVolumeClaims()\n\tpvcCost, pvcCapacity := getPvcCostAndCapacity(pvcs)\n\tmtdSaving := storageCost - pvcCost\n\tprojectedSaving := projectToMonth(mtdSaving)\n\n\tfmt.Printf(\"   %-30s   %d\\n\", \"Unused Volumes:\", len(pvs)-len(pvcs))\n\tfmt.Printf(\"   %-30s   %.2f\\n\", \"Unused Capacity(GB):\", bytesToGB(storageCapacity-pvcCapacity))\n\tfmt.Printf(\"   %-30s   %.2f\\n\", \"Month To Date Savings($):\", mtdSaving)\n\tfmt.Printf(\"   %-30s   %.2f\\n\", \"Projected Monthly Savings($):\", projectedSaving)\n}\n\n// GetPodCost returns the cumulative cost for the pods.\nfunc GetPodCost(podName string) {\n\tpod := getPodDetailsFromClient(podName)\n\tpods := getPodsCost([]*Pod{pod})\n\tprintPodsVerbose(pods)\n}\n\n// GetAllNodesCost returns the cumulative cost of all the nodes.\nfunc GetAllNodesCost() {\n\tnodes := GetClusterNodes()\n\n\tprice := GetUserCosts()\n\thoursInMonthTillNow := totalHoursTillNow()\n\n\tfmt.Println(\"Node name\\tNode cpu-cost\\tNode mem-cost\\tNode total-cost\")\n\tfor i := 0; i < len(nodes); i++ {\n\t\tnode := nodes[i]\n\t\tnodeMetrics := metrics.CalculateNodeStats([]v1.Node{node})\n\n\t\tcpuCost := float64(nodeMetrics.CPULimit.Value()) * hoursInMonthTillNow * price.CPU\n\t\tmemoryCost := bytesToGB(nodeMetrics.MemoryLimit.Value()) * hoursInMonthTillNow * price.Memory\n\t\ttotalComputeCost := cpuCost + memoryCost\n\n\t\tfmt.Printf(\"%s\\t%f\\t%f\\t%f\\n\", node.Name, cpuCost, memoryCost, totalComputeCost)\n\t}\n}\n\nfunc calculateCost(pods []*Pod, pvcs map[string]*PersistentVolumeClaim) []*Pod {\n\tprice := GetUserCosts()\n\tfor i := 0; i <= len(pods)-1; i++ {\n\t\tpod := pods[i]\n\t\tpods[i].cost = calculateCostOfPod(*pod, pvcs, price)\n\t}\n\treturn pods\n}\n\nfunc calculateCostOfPod(pod Pod, pvcs map[string]*PersistentVolumeClaim, price *Price) *Cost {\n\tpodDurationInHours := currentMonthActiveTimeInHours(pod.startTime, metav1.Now())\n\n\tpodCPUCost := float64(pod.podMetrics.CPURequest.Value()) * podDurationInHours * (price.CPU)\n\tpodMemoryCost := bytesToGB(pod.podMetrics.MemoryRequest.Value()) * podDurationInHours * (price.Memory)\n\n\tpodStorageCost := 0.0\n\tfor _, pvc := range pod.pvcs {\n\t\tif pvcs[*pvc] != nil {\n\t\t\tpodStorageCost += pvcs[*pvc].capacityAllotedInGB * podDurationInHours * (price.Storage)\n\t\t} else {\n\t\t\tfmt.Printf(\"Persistent volume claim is not present for %s\\n\", *pvc)\n\t\t}\n\t}\n\tpodTotalCost := podCPUCost + podMemoryCost + podStorageCost\n\treturn &Cost{\n\t\tTotalCost:   podTotalCost,\n\t\tCPUCost:     podCPUCost,\n\t\tMemoryCost:  podMemoryCost,\n\t\tStorageCost: podStorageCost,\n\t}\n}\n\nfunc getPvCostAndCapacity(pvs []v1.PersistentVolume) (float64, int64) {\n\tprice := GetUserCosts()\n\thoursInMonthTillNow := totalHoursTillNow()\n\tvar storageCapacity = resource.Quantity{}\n\tfor _, pv := range pvs {\n\t\tstorageCapacity.Add(pv.Spec.Capacity[\"storage\"])\n\t}\n\tstorageCost := bytesToGB(storageCapacity.Value()) * hoursInMonthTillNow * price.Storage\n\treturn storageCost, storageCapacity.Value()\n}\n\nfunc getPvcCostAndCapacity(pvcs []v1.PersistentVolumeClaim) (float64, int64) {\n\tprice := GetUserCosts()\n\thoursInMonthTillNow := totalHoursTillNow()\n\tvar pvcCapacity = resource.Quantity{}\n\tfor _, pvc := range pvcs {\n\t\tpvcCapacity.Add(pvc.Spec.Resources.Requests[\"storage\"])\n\t}\n\tpvcCost := bytesToGB(pvcCapacity.Value()) * hoursInMonthTillNow * price.Storage\n\treturn pvcCost, pvcCapacity.Value()\n}\n\nfunc getPodsCost(pods []*Pod) []*Pod {\n\tpvcs := map[string]*PersistentVolumeClaim{}\n\tfor _, pod := range pods {\n\t\tfor _, pvc := range pod.pvcs {\n\t\t\tpvcs[*pvc] = nil\n\t\t}\n\t}\n\tpvcs = collectPersistentVolumeClaims(pvcs)\n\tpods = calculateCost(pods, pvcs)\n\treturn pods\n}\n"
  },
  {
    "path": "pkg/plugin/grouping.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n\n\tgroups_v1 \"github.com/vmware/purser/pkg/apis/groups/v1\"\n\tgroups \"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1\"\n)\n\n// GetGroupByName return group CRD by name.\nfunc GetGroupByName(groupClient *groups.GroupClient, groupName string) *groups_v1.Group {\n\tgroup, err := groupClient.Get(groupName)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to get custom group by name %s, %v\", groupName, err)\n\t\treturn nil\n\t}\n\treturn group\n}\n\n// PrintGroup displays the group information.\nfunc PrintGroup(group *groups_v1.Group) {\n\tpitGroupMetrics := group.Spec.PITMetrics\n\tmtdGroupMetrics := group.Spec.MTDMetrics\n\tcost := group.Spec.MTDCost\n\n\tfmt.Printf(\"%-30s             %s\\n\", \"Group Name:\", group.Name)\n\tfmt.Println()\n\n\tif pitGroupMetrics != nil {\n\t\tfmt.Println(\"Point in Time Resource Stats:\")\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"CPU Limit(vCPU):\", pitGroupMetrics.CPULimit)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Memory Limit(GB):\", pitGroupMetrics.MemoryLimit)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"CPU Request(vCPU):\", pitGroupMetrics.CPURequest)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Memory Request(GB):\", pitGroupMetrics.MemoryRequest)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Storage Claimed(GB):\", pitGroupMetrics.StorageClaim)\n\t}\n\n\tif mtdGroupMetrics != nil {\n\t\tfmt.Println()\n\t\tfmt.Printf(\"%-30s\\n\", \"Month to Date Active Resource Stats:\")\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"CPU Request(vCPU-hours):\", mtdGroupMetrics.CPURequest)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Memory Request(GB-hours):\", mtdGroupMetrics.MemoryRequest)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Storage Claimed(GB-hours):\", mtdGroupMetrics.StorageClaim)\n\t}\n\n\tif cost != nil {\n\t\tfmt.Println()\n\t\tfmt.Printf(\"%-30s\\n\", \"Month to Date Cost Stats:\")\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"CPU Cost($):\", cost.CPUCost)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Memory Cost($):\", cost.MemoryCost)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Storage Cost($):\", cost.StorageCost)\n\t\tfmt.Printf(\"             %-30s%.2f\\n\", \"Total Cost($):\", cost.TotalCost)\n\t}\n\n\tfmt.Println()\n\tfmt.Printf(\"Last updated %f minutes ago\", time.Since(group.Spec.LastUpdated).Minutes())\n\tfmt.Println()\n}\n"
  },
  {
    "path": "pkg/plugin/metrics/metrics.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage metrics\n\nimport (\n\t\"fmt\"\n\n\tapi_v1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n)\n\n// Metrics information\ntype Metrics struct {\n\tCPULimit      *resource.Quantity\n\tMemoryLimit   *resource.Quantity\n\tCPURequest    *resource.Quantity\n\tMemoryRequest *resource.Quantity\n}\n\n// GroupMetrics Details\n// Here Active resource is the resource quantity active in the current month\ntype GroupMetrics struct {\n\tCPULimit       float64\n\tMemoryLimit    float64\n\tCPURequest     float64\n\tMemoryRequest  float64\n\tStorageClaimed float64\n}\n\n// CalculatePodStatsFromContainers returns pods stats from containers.\nfunc CalculatePodStatsFromContainers(pods []v1.Pod) *Metrics {\n\tcpuLimit := &resource.Quantity{}\n\tmemoryLimit := &resource.Quantity{}\n\tcpuRequest := &resource.Quantity{}\n\tmemoryRequest := &resource.Quantity{}\n\tfor _, pod := range pods {\n\t\tfor _, c := range pod.Spec.Containers {\n\t\t\tlimits := c.Resources.Limits\n\t\t\tif limits != nil {\n\t\t\t\tcpuLimit.Add(*limits.Cpu())\n\t\t\t\tmemoryLimit.Add(*limits.Memory())\n\t\t\t}\n\n\t\t\trequests := c.Resources.Requests\n\t\t\tif requests != nil {\n\t\t\t\tcpuRequest.Add(*requests.Cpu())\n\t\t\t\tmemoryRequest.Add(*requests.Memory())\n\t\t\t}\n\t\t}\n\t}\n\treturn &Metrics{\n\t\tCPULimit:      cpuLimit,\n\t\tMemoryLimit:   memoryLimit,\n\t\tCPURequest:    cpuRequest,\n\t\tMemoryRequest: memoryRequest,\n\t}\n}\n\n// CalculateNodeStats returns node metrics.\nfunc CalculateNodeStats(nodes []v1.Node) *Metrics {\n\tcpuLimit := &resource.Quantity{}\n\tmemoryLimit := &resource.Quantity{}\n\tcpuRequest := &resource.Quantity{}\n\tmemoryRequest := &resource.Quantity{}\n\tfor _, node := range nodes {\n\t\tcpuLimit.Add(*node.Status.Capacity.Cpu())\n\t\tmemoryLimit.Add(*node.Status.Capacity.Memory())\n\t}\n\treturn &Metrics{\n\t\tCPULimit:      cpuLimit,\n\t\tMemoryLimit:   memoryLimit,\n\t\tCPURequest:    cpuRequest,\n\t\tMemoryRequest: memoryRequest,\n\t}\n}\n\n// PrintPodStats displays stats for Pod\nfunc PrintPodStats(pod *api_v1.Pod, metrics *Metrics) {\n\tfmt.Printf(\"Pod:\\t%s\\n\", pod.Name)\n\tfmt.Printf(\"\\tCPU Limit = %s\\n\", metrics.CPULimit.String())\n\tfmt.Printf(\"\\tMemory Limit = %s\\n\", metrics.MemoryLimit.String())\n\tfmt.Printf(\"\\tCPU Request = %s\\n\", metrics.CPURequest.String())\n\tfmt.Printf(\"\\tMemory Request = %s\\n\", metrics.MemoryRequest.String())\n}\n"
  },
  {
    "path": "pkg/plugin/node.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// GetClusterNodes returns the list of nodes in the cluster.\nfunc GetClusterNodes() []v1.Node {\n\tnodes, err := ClientSetInstance.CoreV1().Nodes().List(metav1.ListOptions{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\treturn nodes.Items\n}\n"
  },
  {
    "path": "pkg/plugin/pod.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/vmware/purser/pkg/plugin/metrics\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\n// Pod Information\ntype Pod struct {\n\tname       string\n\tnodeName   string\n\tcost       *Cost\n\tpvcs       []*string\n\tpodMetrics *metrics.Metrics\n\tstartTime  metav1.Time\n}\n\n// GetClusterPods returns the list of pods in cluster.\nfunc GetClusterPods() []v1.Pod {\n\tpods, err := ClientSetInstance.CoreV1().Pods(\"\").List(metav1.ListOptions{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\treturn pods.Items\n}\n\nfunc getPodDetailsFromClient(podName string) *Pod {\n\tpod, err := ClientSetInstance.CoreV1().Pods(\"default\").Get(podName, metav1.GetOptions{})\n\tif errors.IsNotFound(err) {\n\t\tfmt.Printf(\"Node %s not found\\n\", podName)\n\t\treturn nil\n\t} else if statusError, isStatus := err.(*errors.StatusError); isStatus {\n\t\tfmt.Printf(\"Error getting Node %s : %v\\n\", podName, statusError.ErrStatus.Message)\n\t\treturn nil\n\t} else if err != nil {\n\t\tpanic(err.Error())\n\t} else {\n\t\treturn &Pod{\n\t\t\tname:       pod.GetObjectMeta().GetName(),\n\t\t\tnodeName:   pod.Spec.NodeName,\n\t\t\tpvcs:       getPodVolumes(pod),\n\t\t\tpodMetrics: metrics.CalculatePodStatsFromContainers([]v1.Pod{*pod}),\n\t\t\tstartTime:  *pod.Status.StartTime,\n\t\t}\n\t}\n}\n\nfunc getPodsForLabelThroughClient(label string) []*Pod {\n\tvals := strings.Split(label, \"=\")\n\tif len(vals) != 2 {\n\t\tpanic(\"Label should be of form key=val\")\n\t}\n\n\tm := map[string]string{vals[0]: vals[1]}\n\tpods, err := ClientSetInstance.CoreV1().Pods(\"\").List(metav1.ListOptions{LabelSelector: labels.SelectorFromSet(m).String()})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\n\treturn createPodObjects(pods)\n}\n\nfunc createPodObjects(pods *v1.PodList) []*Pod {\n\tps := []*Pod{}\n\n\tfor i := 0; i < len(pods.Items); i++ {\n\t\tp := createPodObject(&pods.Items[i])\n\t\tps = append(ps, &p)\n\t}\n\treturn ps\n}\n\nfunc createPodObject(pod *v1.Pod) Pod {\n\treturn Pod{\n\t\tname:       pod.GetObjectMeta().GetName(),\n\t\tnodeName:   pod.Spec.NodeName,\n\t\tpvcs:       getPodVolumes(pod),\n\t\tpodMetrics: metrics.CalculatePodStatsFromContainers([]v1.Pod{*pod}),\n\t\tstartTime:  *pod.Status.StartTime,\n\t}\n}\n\nfunc getPodVolumes(pod *v1.Pod) []*string {\n\tpodVolumes := []*string{}\n\tfor j := 0; j < len(pod.Spec.Volumes); j++ {\n\t\tvol := pod.Spec.Volumes[j]\n\t\tif vol.PersistentVolumeClaim != nil {\n\t\t\tpodVolumes = append(podVolumes, &vol.PersistentVolumeClaim.ClaimName)\n\t\t}\n\t}\n\treturn podVolumes\n}\n\nfunc printPodsVerbose(pods []*Pod) {\n\tfmt.Printf(\"Cost Summary\\n\")\n\ttotalCost := 0.0\n\ttotalCPUCost := 0.0\n\ttotalMemoryCost := 0.0\n\ttotalStorageCost := 0.0\n\n\tfor i := 0; i <= len(pods)-1; i++ {\n\t\tfmt.Printf(\"%-30s    %s\\n\", \"Pod Name:\", pods[i].name)\n\t\tfmt.Printf(\"%-30s    %s\\n\", \"Node:\", pods[i].nodeName)\n\t\tfmt.Printf(\"%-30s\\n\", \"Persistent Volume Claims:\")\n\n\t\tfor j := 0; j <= len(pods[i].pvcs)-1; j++ {\n\t\t\tfmt.Printf(\"    %s\\n\", *pods[i].pvcs[j])\n\n\t\t}\n\t\tfmt.Printf(\"%-30s\\n\", \"Cost:\")\n\t\tfmt.Printf(\"    %-30s%f$\\n\", \"Total Cost:\", pods[i].cost.TotalCost)\n\t\tfmt.Printf(\"    %-30s%f$\\n\", \"Compute Cost:\", pods[i].cost.CPUCost+pods[i].cost.MemoryCost)\n\t\tfmt.Printf(\"    %-30s%f$\\n\", \"Storage Cost:\", pods[i].cost.StorageCost)\n\t\tfmt.Printf(\"\\n\")\n\n\t\ttotalCost += pods[i].cost.TotalCost\n\t\ttotalCPUCost += pods[i].cost.CPUCost\n\t\ttotalMemoryCost += pods[i].cost.MemoryCost\n\t\ttotalStorageCost += pods[i].cost.StorageCost\n\t}\n\n\tfmt.Printf(\"%-30s\\n\", \"Total Cost Summary:\")\n\tfmt.Printf(\"    %-30s%f$\\n\", \"Total Cost:\", totalCost)\n\tfmt.Printf(\"    %-30s%f$\\n\", \"Compute Cost:\", totalCPUCost+totalMemoryCost)\n\tfmt.Printf(\"    %-30s%f$\\n\", \"Storage Cost:\", totalStorageCost)\n}\n"
  },
  {
    "path": "pkg/plugin/pricing.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst (\n\tnamespace                      = \"default\"\n\tuserCostsConfigMap             = \"purser-user-costs\"\n\tdefaultCPUCostPerCPUPerHour    = 0.024\n\tdefaultMemCostPerGBPerHour     = 0.01\n\tdefaultStorageCostPerGBPerHour = 0.00013888888\n)\n\n// Price information.\n// NOTE: All fields are Per unit resource per hour\ntype Price struct {\n\tCPU     float64\n\tMemory  float64\n\tStorage float64\n}\n\n// SaveUserCosts stores the cpu, memory and storage cost per unit per hour in the cluster as config maps.\nfunc SaveUserCosts(cpuCostPerCPUPerHour, memCostPerGBPerHour, storageCostPerGBPerHour string) bool {\n\tcm, err := ClientSetInstance.CoreV1().ConfigMaps(namespace).Get(userCostsConfigMap, metav1.GetOptions{})\n\tif err != nil {\n\t\t// no configmap so create new one\n\t\tcostMap := map[string]string{}\n\t\tcostMap[\"cpuCostPerCPUPerHour\"] = cpuCostPerCPUPerHour\n\t\tcostMap[\"memCostPerGBPerHour\"] = memCostPerGBPerHour\n\t\tcostMap[\"storageCostPerGBPerHour\"] = storageCostPerGBPerHour\n\t\tcm = &v1.ConfigMap{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: userCostsConfigMap,\n\t\t\t},\n\t\t\tData: costMap,\n\t\t}\n\t\t_, err2 := ClientSetInstance.CoreV1().ConfigMaps(namespace).Create(cm)\n\t\tif err2 != nil {\n\t\t\tfmt.Printf(\"Error in createing config map: %s\", err2)\n\t\t\treturn false\n\t\t}\n\t} else {\n\t\t// update configmap\n\t\tcm.Data[\"cpuCostPerCPUPerHour\"] = cpuCostPerCPUPerHour\n\t\tcm.Data[\"memCostPerGBPerHour\"] = memCostPerGBPerHour\n\t\tcm.Data[\"storageCostPerGBPerHour\"] = storageCostPerGBPerHour\n\t\t_, err2 := ClientSetInstance.CoreV1().ConfigMaps(namespace).Update(cm)\n\t\tif err2 != nil {\n\t\t\tfmt.Printf(\"Error in updating config map: %s\", err2)\n\t\t\treturn false\n\t\t}\n\t\tfmt.Printf(\"Updated config map\\n\")\n\t}\n\n\treturn true\n}\n\n// GetUserCosts gives the cpu, memory and storage cost per unit per hour which are stored in the cluster as config maps.\nfunc GetUserCosts() *Price {\n\tvar cpuCostPerCPUPerHour, memCostPerGBPerHour, storageCostPerGBPerHour float64\n\tcm, err := ClientSetInstance.CoreV1().ConfigMaps(namespace).Get(userCostsConfigMap, metav1.GetOptions{})\n\tif err != nil {\n\t\t// no user configed costs. so return default values\n\t\tcpuCostPerCPUPerHour = defaultCPUCostPerCPUPerHour\n\t\tmemCostPerGBPerHour = defaultMemCostPerGBPerHour\n\t\tstorageCostPerGBPerHour = defaultStorageCostPerGBPerHour\n\t} else {\n\t\tcpuCostPerCPUPerHour, err = strconv.ParseFloat(cm.Data[\"cpuCostPerCPUPerHour\"], 64)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error converting cpuCostPerCPUPerHour string %s to float, rolling back to default value\\n\",\n\t\t\t\tcm.Data[\"cpuCostPerCPUPerHour\"])\n\t\t\tcpuCostPerCPUPerHour = defaultCPUCostPerCPUPerHour\n\t\t}\n\n\t\tmemCostPerGBPerHour, err = strconv.ParseFloat(cm.Data[\"memCostPerGBPerHour\"], 64)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error converting memCostPerGBPerHour string %s to float, rolling back to default value\\n\",\n\t\t\t\tcm.Data[\"memCostPerGBPerHour\"])\n\t\t\tmemCostPerGBPerHour = defaultMemCostPerGBPerHour\n\t\t}\n\n\t\tstorageCostPerGBPerHour, err = strconv.ParseFloat(cm.Data[\"storageCostPerGBPerHour\"], 64)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error converting storageCostPerGBPerHour string %s to float, rolling back to default value\\n\",\n\t\t\t\tcm.Data[\"storageCostPerGBPerHour\"])\n\t\t\tstorageCostPerGBPerHour = defaultStorageCostPerGBPerHour\n\t\t}\n\t}\n\n\treturn &Price{\n\t\tCPU:     cpuCostPerCPUPerHour,\n\t\tMemory:  memCostPerGBPerHour,\n\t\tStorage: storageCostPerGBPerHour,\n\t}\n}\n"
  },
  {
    "path": "pkg/plugin/utils.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\t\"time\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// getCurrentTime returns the current time as k8s apimachinery Time object\nfunc getCurrentTime() metav1.Time {\n\treturn metav1.Now()\n}\n\n// getCurrentMonthStartTime returns month start time as k8s apimachinery Time object\nfunc getCurrentMonthStartTime() metav1.Time {\n\tnow := time.Now()\n\tmonthStart := metav1.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)\n\treturn monthStart\n}\n\n/*\ncurrentMonthActiveTimeInHours returns active time (endTime - startTime) in the current month.\n1. If startTime is before month start then it is set as month start\n2. If endTime is not set(isZero) then it is set as current time\nThese two conditions ensures that the active time we compute is within the current month.\n*/\nfunc currentMonthActiveTimeInHours(startTime, endTime metav1.Time) float64 {\n\tcurrentTime := getCurrentTime()\n\tmonthStart := getCurrentMonthStartTime()\n\treturn currentMonthActiveTimeInHoursMulti(startTime, endTime, currentTime, monthStart)\n}\n\n/*\ncurrentMonthActiveTimeInHoursMulti is same as currentMonthActiveTimeInHours but it needs extra inputs:\ncurrentTime and monthStart.\nUse this method(currentMonthActiveTimeInHoursMulti) if you want to caclculate active time multiple times (ex: inside a loop).\n*/\nfunc currentMonthActiveTimeInHoursMulti(startTime, endTime, currentTime, monthStart metav1.Time) float64 {\n\tif startTime.Time.Before(monthStart.Time) {\n\t\tstartTime = monthStart\n\t}\n\n\tif endTime.IsZero() {\n\t\tendTime = currentTime\n\t}\n\n\tduration := endTime.Time.Sub(startTime.Time)\n\tdurationInHours := duration.Hours()\n\treturn durationInHours\n}\n\n// totalHoursTillNow return number of hours from month start to current time.\nfunc totalHoursTillNow() float64 {\n\tmonthStart := getCurrentMonthStartTime()\n\tcurrentTime := getCurrentTime()\n\treturn currentMonthActiveTimeInHours(monthStart, currentTime)\n}\n\nfunc projectToMonth(val float64) float64 {\n\t// TODO: enhance this.\n\treturn (val * 31 * 24) / totalHoursTillNow()\n}\n\nfunc bytesToGB(val int64) float64 {\n\treturn float64(val) / (1024.0 * 1024.0 * 1024.0)\n}\n"
  },
  {
    "path": "pkg/plugin/volume.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\n\t\"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// PersistentVolumeClaim details\ntype PersistentVolumeClaim struct {\n\tname                string\n\tvolumeName          string\n\trequestSizeInGB     float64\n\tcapacityAllotedInGB float64\n\tstorageClass        *string\n}\n\n// GetClusterVolumes returns list of persistent volumes for the cluster.\nfunc GetClusterVolumes() []v1.PersistentVolume {\n\tpvs, err := ClientSetInstance.CoreV1().PersistentVolumes().List(metav1.ListOptions{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\treturn pvs.Items\n}\n\n// GetClusterPersistentVolumeClaims returns the list of persistent volume claims for the cluster.\nfunc GetClusterPersistentVolumeClaims() []v1.PersistentVolumeClaim {\n\tpvcs, err := ClientSetInstance.CoreV1().PersistentVolumeClaims(\"\").List(metav1.ListOptions{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\treturn pvcs.Items\n}\n\nfunc collectPersistentVolumeClaims(pvcs map[string]*PersistentVolumeClaim) map[string]*PersistentVolumeClaim {\n\tfor key := range pvcs {\n\t\tpvc := collectPersistentVolumeClaim(key)\n\t\tpvcs[key] = pvc\n\t}\n\treturn pvcs\n}\n\nfunc collectPersistentVolumeClaim(claimName string) *PersistentVolumeClaim {\n\tpvc, err := ClientSetInstance.CoreV1().PersistentVolumeClaims(\"default\").Get(claimName, metav1.GetOptions{})\n\tif errors.IsNotFound(err) {\n\t\tfmt.Printf(\"Persistent Volume Claim %s not found\\n\", claimName)\n\t\treturn nil\n\t} else if statusError, isStatus := err.(*errors.StatusError); isStatus {\n\t\tfmt.Printf(\"Error getting persistence volume Claim %s : %v\\n\", claimName, statusError.ErrStatus.Message)\n\t\treturn nil\n\t} else if err != nil {\n\t\tpanic(err.Error())\n\t} else {\n\t\trequest := pvc.Spec.Resources.Requests[\"storage\"].DeepCopy()\n\t\tcapacity := pvc.Status.Capacity[\"storage\"].DeepCopy()\n\n\t\treturn &PersistentVolumeClaim{\n\t\t\tname:                pvc.GetObjectMeta().GetName(),\n\t\t\tvolumeName:          pvc.Spec.VolumeName,\n\t\t\tstorageClass:        pvc.Spec.StorageClassName,\n\t\t\trequestSizeInGB:     bytesToGB(request.Value()),\n\t\t\tcapacityAllotedInGB: bytesToGB(capacity.Value()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/pricing/aws/aws.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage aws\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/utils\"\n)\n\nconst (\n\thttpTimeout = 100 * time.Second\n)\n\n// Pricing structure\ntype Pricing struct {\n\tProducts map[string]Product\n\tTerms    PlanList\n}\n\n// PlanList structure\ntype PlanList struct {\n\tOnDemand map[string]map[string]TermAttributes\n}\n\n// TermAttributes structure\ntype TermAttributes struct {\n\tPriceDimensions map[string]PricingData\n}\n\n// PricingData structure\ntype PricingData struct {\n\tUnit         string\n\tPricePerUnit map[string]string\n}\n\n// Product structure\ntype Product struct {\n\tSku           string\n\tProductFamily string\n\tAttributes    ProductAttributes\n}\n\n// ProductAttributes structure\ntype ProductAttributes struct {\n\tInstanceType    string\n\tInstanceFamily  string\n\tOperatingSystem string\n\tPreInstalledSW  string\n\tVolumeType      string\n\tUsageType       string\n\tVcpu            string\n\tMemory          string\n}\n\n// GetAWSPricing function details\n// input: region\n// retrieves data from http get to the corresponding url for that region\nfunc GetAWSPricing(region string) (*Pricing, error) {\n\tvar myClient = &http.Client{Timeout: httpTimeout}\n\trateCard := Pricing{}\n\terr := utils.GetJSONResponse(myClient, getURLForRegion(region), &rateCard)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Unable to get aws pricing. Reason: %v\", err)\n\t\treturn nil, err\n\t}\n\treturn &rateCard, nil\n}\n\nfunc getURLForRegion(region string) string {\n\treturn \"https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/\" + region + \"/index.json\"\n}\n"
  },
  {
    "path": "pkg/pricing/aws/convert.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage aws\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n)\n\n// AWS specific constants\nconst (\n\tna              = \"NA\"\n\tgbMonth         = \"GB-Mo\"\n\tdeliminator     = \"-\"\n\tstorageInstance = \"Storage\"\n\tcomputeInstance = \"Compute Instance\"\n\n\t// TODO: Determine priceSplitRatio according to instance type i.e, compute optimized or memory optimized etc\n\tpriceSplitRatio = 0.5\n)\n\n// GetRateCardForAWS takes region as input and returns RateCard and error if any\nfunc GetRateCardForAWS(region string) *models.RateCard {\n\tawsPricing, err := GetAWSPricing(region)\n\tif err == nil {\n\t\treturn convertAWSPricingToPurserRateCard(region, awsPricing)\n\t}\n\treturn nil\n}\n\nfunc convertAWSPricingToPurserRateCard(region string, awsPricing *Pricing) *models.RateCard {\n\tnodePrices, storagePrices := getResourcePricesFromAWSPricing(awsPricing)\n\treturn &models.RateCard{\n\t\tID:            dgraph.ID{Xid: models.RateCardXID},\n\t\tIsRateCard:    true,\n\t\tCloudProvider: models.AWS,\n\t\tRegion:        region,\n\t\tNodePrices:    nodePrices,\n\t\tStoragePrices: storagePrices,\n\t}\n}\n\nfunc getResourcePricesFromAWSPricing(awsPricing *Pricing) ([]*models.NodePrice, []*models.StoragePrice) {\n\tvar nodePrices []*models.NodePrice\n\tvar storagePrices []*models.StoragePrice\n\tproducts := awsPricing.Products\n\tplanList := awsPricing.Terms\n\n\tduplicateComputeInstanceChecker := make(map[string]bool)\n\tfor _, product := range products {\n\t\tpriceInFloat64, unit := getResourcePrice(product, planList)\n\t\tswitch product.ProductFamily {\n\t\tcase computeInstance:\n\t\t\tnodePrices = updateComputeInstancePrices(product, priceInFloat64, duplicateComputeInstanceChecker, nodePrices)\n\t\tcase storageInstance:\n\t\t\tstoragePrices = updateStorageInstancePrices(product, priceInFloat64, unit, storagePrices)\n\t\t}\n\t}\n\treturn nodePrices, storagePrices\n}\n\nfunc getResourcePrice(product Product, planList PlanList) (float64, string) {\n\tfor _, pricingAttributes := range planList.OnDemand[product.Sku] {\n\t\tfor _, pricingData := range pricingAttributes.PriceDimensions {\n\t\t\tfor _, pricePerUnit := range pricingData.PricePerUnit {\n\t\t\t\tpriceInFloat64, err := strconv.ParseFloat(pricePerUnit, 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.Errorf(\"unable to parse string: %s to float. err: %v\", pricePerUnit, err)\n\t\t\t\t\treturn models.PriceError, \"\" // negative price means error\n\t\t\t\t}\n\t\t\t\treturn priceInFloat64, pricingData.Unit\n\t\t\t}\n\t\t}\n\t}\n\treturn models.PriceError, \"\"\n}\n\nfunc updateComputeInstancePrices(product Product, priceInFloat64 float64, duplicateComputeInstanceChecker map[string]bool, nodePrices []*models.NodePrice) []*models.NodePrice {\n\tkey := product.Sku + product.Attributes.InstanceType + product.Attributes.OperatingSystem\n\tif _, isPresent := duplicateComputeInstanceChecker[key]; !isPresent && product.Attributes.PreInstalledSW == na {\n\t\t// Unit of Compute price USD-perHour\n\t\tproductXID := product.Attributes.InstanceType + deliminator + product.Attributes.OperatingSystem\n\t\tpricePerCPU, pricePerGB := getPriceForUnitResource(product, priceInFloat64)\n\t\tnodePrice := &models.NodePrice{\n\t\t\tID:              dgraph.ID{Xid: productXID},\n\t\t\tIsNodePrice:     true,\n\t\t\tInstanceType:    product.Attributes.InstanceType,\n\t\t\tInstanceFamily:  product.Attributes.InstanceFamily,\n\t\t\tOperatingSystem: product.Attributes.OperatingSystem,\n\t\t\tPrice:           priceInFloat64,\n\t\t\tPricePerCPU:     pricePerCPU,\n\t\t\tPricePerMemory:  pricePerGB,\n\t\t}\n\t\tduplicateComputeInstanceChecker[key] = true\n\t\tuid := models.StoreNodePrice(nodePrice, productXID)\n\t\tif uid != \"\" {\n\t\t\tnodePrice.ID = dgraph.ID{UID: uid, Xid: productXID}\n\t\t\tnodePrices = append(nodePrices, nodePrice)\n\t\t}\n\t}\n\treturn nodePrices\n}\n\nfunc updateStorageInstancePrices(product Product, priceInFloat64 float64, unit string, storagePrices []*models.StoragePrice) []*models.StoragePrice {\n\tif priceInFloat64 == models.PriceError {\n\t\tpriceInFloat64 = models.DefaultStorageCostInFloat64\n\t} else if unit == gbMonth {\n\t\t// convert to GBHour\n\t\tpriceInFloat64 = priceInFloat64 / models.HoursInMonth\n\t}\n\n\tproductXID := product.Attributes.VolumeType + deliminator + product.Attributes.UsageType\n\tstoragePrice := &models.StoragePrice{\n\t\tID:             dgraph.ID{Xid: productXID},\n\t\tIsStoragePrice: true,\n\t\tVolumeType:     product.Attributes.VolumeType,\n\t\tUsageType:      product.Attributes.UsageType,\n\t\tPrice:          priceInFloat64,\n\t}\n\tuid := models.StoreStoragePrice(storagePrice, productXID)\n\tif uid != \"\" {\n\t\tstoragePrice.ID = dgraph.ID{UID: uid, Xid: productXID}\n\t\tstoragePrices = append(storagePrices, storagePrice)\n\t}\n\treturn storagePrices\n}\n\nfunc getPriceForUnitResource(product Product, priceInFloat64 float64) (float64, float64) {\n\tpricePerCPU := models.DefaultCPUCostInFloat64\n\tpricePerGB := models.DefaultMemCostInFloat64\n\n\t// priceInFloat64 should be greater than 0 otherwise this function returns default pricing\n\tif priceInFloat64 != models.PriceError && priceInFloat64 != 0 {\n\t\tcpu, err := strconv.ParseFloat(product.Attributes.Vcpu, 64)\n\t\tif err == nil {\n\t\t\tpricePerCPU = priceSplitRatio * priceInFloat64 / cpu\n\t\t}\n\n\t\tmemWithUnits := product.Attributes.Memory\n\t\t// memWithUnits format: \"3,126 GiB\"\n\t\tmem, err := strconv.ParseFloat(strings.Join(strings.Split(strings.Split(memWithUnits, \" GiB\")[0], \",\"), \"\"), 64)\n\t\tif err == nil {\n\t\t\tpricePerGB = (1 - priceSplitRatio) * priceInFloat64 / mem\n\t\t}\n\t}\n\treturn pricePerCPU, pricePerGB\n}\n"
  },
  {
    "path": "pkg/pricing/cloud.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pricing\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\t\"github.com/vmware/purser/pkg/pricing/aws\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// Cloud structure used for pricing\ntype Cloud struct {\n\tCloudProvider string\n\tRegion        string\n\tKubeclient    *kubernetes.Clientset\n}\n\n// GetClusterProviderAndRegion returns cluster provider(ex: aws) and region(ex: us-east-1)\nfunc GetClusterProviderAndRegion() (string, string) {\n\t// TODO: https://github.com/vmware/purser/issues/143\n\tcloudProvider := models.AWS\n\tregion := \"us-east-1\"\n\tlogrus.Infof(\"CloudProvider: %s, Region: %s\", cloudProvider, region)\n\treturn cloudProvider, region\n}\n\n// PopulateRateCard given a cloud (cloudProvider and region) it populates corresponding rate card in dgraph\nfunc (c *Cloud) PopulateRateCard() {\n\tswitch c.CloudProvider {\n\tcase models.AWS:\n\t\trateCard := aws.GetRateCardForAWS(c.Region)\n\t\tmodels.StoreRateCard(rateCard)\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/fileutils.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"os\"\n\t\"os/user\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\n// OpenFile handles opening file in Read/Write mode, creating and appending to it as needed.\nfunc OpenFile(filename string) *os.File {\n\tf, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to open file %s, %v\", filename, err)\n\t}\n\treturn f\n}\n\n// GetUsrHomeDir returns the current user's Home Directory\nfunc GetUsrHomeDir() string {\n\tusr, err := user.Current()\n\tif err != nil {\n\t\tlog.Errorf(\"failed to fetch current user %v\", err)\n\t}\n\treturn usr.HomeDir\n}\n"
  },
  {
    "path": "pkg/utils/k8sutil.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"github.com/Sirupsen/logrus\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\n// GetKubeclient returns a k8s clientset from the kubeconfig, if nil fallback to\n// client from inCluster config.\nfunc GetKubeclient(config *rest.Config) *kubernetes.Clientset {\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\tlogrus.Fatalf(\"failed to create kubernetes clientset: %v\", err)\n\t}\n\treturn clientset\n}\n\n// GetKubeconfig builds config from the kubeconfig path, if nil fallback to\n// inCluster config.\nfunc GetKubeconfig(kubeconfigPath string) (*rest.Config, error) {\n\treturn clientcmd.BuildConfigFromFlags(\"\", kubeconfigPath)\n}\n"
  },
  {
    "path": "pkg/utils/logutil.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\nconst logFile = \"purser.log\"\n\n// InitializeLogger sets and configures logger options.\nfunc InitializeLogger(logLevel string) {\n\tlogFile := OpenFile(logFile)\n\n\tlog.SetOutput(io.MultiWriter(os.Stdout, logFile))\n\tlog.SetFormatter(&log.TextFormatter{ForceColors: true})\n\n\tif logLevel == \"debug\" {\n\t\tlog.SetLevel(log.DebugLevel)\n\t} else {\n\t\tlog.SetLevel(log.InfoLevel)\n\t}\n}\n"
  },
  {
    "path": "plugin.yaml",
    "content": "name: \"purser\"\nshortDesc: \"Cost Insight integration with kubernetes\"\nlongDesc: >\n  Purser gives cost insights of kubernetes deployments.\ncommand: purser_plugin $@\nflags:\n  - name: info\n    desc: Show more details about the plugin.\n  - name: version\n    desc: Show plugin version\n"
  },
  {
    "path": "test/controller/buffering/ring_buffer_test.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage buffering_test\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/vmware/purser/pkg/controller/buffering\"\n\t\"github.com/vmware/purser/test/utils\"\n)\n\nfunc TestPut(t *testing.T) {\n\t// use Put to add one more, return from Put should be True\n\tr := &buffering.RingBuffer{Size: 2, Mutex: &sync.Mutex{}}\n\n\ttestValue1 := 1\n\tret1 := r.Put(testValue1)\n\tutils.Assert(t, ret1, \"inserting into not full buffer\")\n\n\ttestValue2 := 38\n\tret2 := r.Put(testValue2)\n\tutils.Assert(t, !ret2, \"inserting into full buffer\")\n}\n\nfunc TestGet(t *testing.T) {\n\t// use Put to add one more, return from Put should be True\n\tr := &buffering.RingBuffer{Size: 2, Mutex: &sync.Mutex{}}\n\n\tret1 := r.Get()\n\tutils.Assert(t, ret1 == nil, \"get elements of empty buffer\")\n\n\ttestValue := 1\n\tr.Put(testValue)\n\tret2 := r.Get()\n\tutils.Assert(t, (*ret2).(int) == testValue, \"get elements from non empty buffer\")\n}\n"
  },
  {
    "path": "test/pricing/pricing_aws_test.go",
    "content": "package pricing\n\nimport (\n\t\"testing\"\n\n\t\"github.com/vmware/purser/test/utils\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph\"\n\t\"github.com/vmware/purser/pkg/controller/dgraph/models\"\n\t\"github.com/vmware/purser/pkg/pricing/aws\"\n)\n\n// TestAWSPricingFlow it should populate your dgraph running at localhost 9080 port with aws compute and storage prices\n// The following dgraph query will give the rate card data\n// {\n//\t\trateCard(func: has(isRateCard)) {\n//\t\t\tcloudProvider\n//\t\t\tregion\n//\t\t\tnodePrices {\n//\t\t\t\tinstanceType\n//\t\t\t\toperatingSystem\n//\t\t\t\tprice\n//\t\t\t\tinstanceFamily\n//\t\t\t}\n//\t\t\tstoragePrices {\n//\t\t\t\tvolumeType\n//\t\t\t\tusageType\n//\t\t\t\tprice\n//\t\t\t}\n//\t\t}\n// }\nfunc TestAWSPricingFlow(t *testing.T) {\n\tlogrus.SetLevel(logrus.DebugLevel)\n\tdgraph.Start(\"localhost\", \"9080\")\n\trateCard := aws.GetRateCardForAWS(\"us-east-1\")\n\tmodels.StoreRateCard(rateCard)\n\tdefer dgraph.Close()\n\tutils.Assert(t, rateCard != nil, \"rate card is nil\")\n}\n"
  },
  {
    "path": "test/utils/checkUtil.go",
    "content": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n)\n\n// Assert fails the test if the condition is false.\nfunc Assert(tb testing.TB, condition bool, msg string, v ...interface{}) {\n\tif !condition {\n\t\t_, file, line, _ := runtime.Caller(1)\n\t\tfmt.Printf(\"\\033[31m%s:%d: \"+msg+\"\\033[39m\\n\\n\", append([]interface{}{filepath.Base(file), line}, v...)...)\n\t\ttb.FailNow()\n\t}\n}\n\n// Ok fails the test if an err is not nil.\nfunc Ok(tb testing.TB, err error) {\n\tif err != nil {\n\t\t_, file, line, _ := runtime.Caller(1)\n\t\tfmt.Printf(\"\\033[31m%s:%d: unexpected error: %s\\033[39m\\n\\n\", filepath.Base(file), line, err.Error())\n\t\ttb.FailNow()\n\t}\n}\n\n// Equals fails the test if exp is not equal to act.\nfunc Equals(tb testing.TB, exp, act interface{}) {\n\tif !reflect.DeepEqual(exp, act) {\n\t\t_, file, line, _ := runtime.Caller(1)\n\t\tfmt.Printf(\"\\033[31m%s:%d:\\n\\n\\texp: %#v\\n\\n\\tgot: %#v\\033[39m\\n\\n\", filepath.Base(file), line, exp, act)\n\t\ttb.FailNow()\n\t}\n}\n"
  },
  {
    "path": "ui/Dockerfile.deploy.purser",
    "content": "FROM node:9.6.1 as builder\n\nLABEL maintainer = \"VMware <kreddyj@vmware.com>\"\nLABEL author = \"Krishna Karthik <kreddyj@vmware.com>\"\n\n# set working directory\nRUN mkdir /usr/src/app\nWORKDIR /usr/src/app\n\n# add `/usr/src/app/node_modules/.bin` to $PATH\nENV PATH /usr/src/app/node_modules/.bin:$PATH\n\n# install and cache app dependencies\nCOPY package.json package-lock.json ./\nRUN npm install\nRUN npm install -g @angular/cli@6.2.1\n\n# add purser application to the working directory\nCOPY . .\n\n# start purser application\nRUN npm run build\n\n# Build a small nginx image\nFROM nginx:latest\nCOPY nginx.conf /etc/nginx/conf.d/default.conf\nCOPY --from=builder /usr/src/app/dist /usr/share/nginx/html"
  },
  {
    "path": "ui/README.md",
    "content": "# Purser UI\n\nPurser UI is designed to provide a visual representation to a host of features provided by Purser such as **cluster hierarchy**, **Pod and Service interactions** and **capacity allocations** for CPU, memory, disk space and other resources.    \n \n It has been generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.2.1 and [Clarity Design System](https://clarity.design/).\n\n## Installing Dependencies\n\nUse \"npm\" or \"yarn\" to install/manage dependencies. Run `npm install` inside this directory to install all the needed dependencies.\n\n## Development server\n\nRun `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `npm build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md)."
  },
  {
    "path": "ui/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"purser\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist/purser\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\",\n              \"src/json\"\n            ],\n            \"styles\": [\n              \"node_modules/@clr/icons/clr-icons.min.css\",\n              \"node_modules/@clr/ui/clr-ui.min.css\",\n              \"src/styles.css\",\n              \"node_modules/vis/dist/vis.css\"\n            ],\n            \"scripts\": [\n              \"node_modules/@webcomponents/custom-elements/custom-elements.min.js\",\n              \"node_modules/@clr/icons/clr-icons.min.js\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"purser:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"purser:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"purser:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"src/styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    },\n    \"purser-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"purser:serve\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"devServerTarget\": \"purser:serve:production\"\n            }\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"purser\"\n}"
  },
  {
    "path": "ui/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require('jasmine-spec-reporter');\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\n    './src/**/*.e2e-spec.ts'\n  ],\n  capabilities: {\n    'browserName': 'chrome'\n  },\n  directConnect: true,\n  baseUrl: 'http://localhost:4200/',\n  framework: 'jasmine',\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require('ts-node').register({\n      project: require('path').join(__dirname, './tsconfig.e2e.json')\n    });\n    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};"
  },
  {
    "path": "ui/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('workspace-project App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getParagraphText()).toEqual('Welcome to purser!');\n  });\n});\n"
  },
  {
    "path": "ui/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getParagraphText() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "ui/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "ui/nginx.conf",
    "content": "upstream purser {\n    server purser.purser.svc.cluster.local:3030;\n}\n\nserver {\n    listen 4200;\n\n    location /auth {\n        proxy_pass http://purser;\n    }\n\n    location /api {\n        proxy_pass http://purser;\n    }\n\n    location / {\n        root /usr/share/nginx/html/purser;\n        index index.html index.htm;\n        try_files $uri $uri/ /index.html =404;\n    }\n}"
  },
  {
    "path": "ui/package.json",
    "content": "{\n  \"name\": \"app-dapp\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"startdev\": \"ng serve --proxy-config proxy.conf.json\",\n    \"build\": \"ng build\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular-devkit/build-angular\": \"~0.13.9\",\n    \"@angular/animations\": \"^6.1.0\",\n    \"@angular/common\": \"^6.1.0\",\n    \"@angular/compiler\": \"^6.1.0\",\n    \"@angular/core\": \"^6.1.0\",\n    \"@angular/forms\": \"^6.1.0\",\n    \"@angular/http\": \"^6.1.0\",\n    \"@angular/platform-browser\": \"^6.1.0\",\n    \"@angular/platform-browser-dynamic\": \"^6.1.0\",\n    \"@angular/router\": \"^6.1.0\",\n    \"@clr/angular\": \"^0.13.1-patch.1\",\n    \"@clr/icons\": \"^0.13.1-patch.1\",\n    \"@clr/ui\": \"^0.13.1-patch.1\",\n    \"@types/vis\": \"^4.21.8\",\n    \"@webcomponents/custom-elements\": \"^1.0.0\",\n    \"angular-google-charts\": \"^0.1.0\",\n    \"core-js\": \"^2.5.4\",\n    \"ngx-cookie-service\": \"^2.1.0\",\n    \"rxjs\": \"~6.2.0\",\n    \"vis\": \"^4.21.0\",\n    \"zone.js\": \"~0.8.26\"\n  },\n  \"devDependencies\": {\n    \"@angular/cli\": \"~6.2.1\",\n    \"@angular/compiler-cli\": \"^6.1.0\",\n    \"@angular/language-service\": \"^6.1.0\",\n    \"@types/jasmine\": \"~2.8.8\",\n    \"@types/jasminewd2\": \"~2.0.3\",\n    \"@types/node\": \"~8.9.4\",\n    \"codelyzer\": \"~4.3.0\",\n    \"jasmine-core\": \"~2.99.1\",\n    \"jasmine-spec-reporter\": \"~4.2.1\",\n    \"karma\": \"~3.0.0\",\n    \"karma-chrome-launcher\": \"~2.2.0\",\n    \"karma-coverage-istanbul-reporter\": \"~2.0.1\",\n    \"karma-jasmine\": \"~1.1.2\",\n    \"karma-jasmine-html-reporter\": \"^0.2.2\",\n    \"protractor\": \"~5.4.0\",\n    \"ts-node\": \"~7.0.0\",\n    \"tslint\": \"~5.11.0\",\n    \"typescript\": \"~2.9.2\"\n  }\n}\n"
  },
  {
    "path": "ui/proxy.conf.json",
    "content": "{\n    \"/api\": {\n     \"target\": \"http://localhost:3030/\",\n     \"changeOrigin\": true,\n     \"secure\":false\n    }\n}"
  },
  {
    "path": "ui/src/app/app.component.html",
    "content": "<div class=\"main-container\">\n  <header class=\"header header-6\">\n    <div class=\"branding appHeader\">\n      <span>{{messages && messages.common.appHeader}}</span>\n    </div>\n  </header>\n  <div class=\"content-container\">\n    <div class=\"main-body\">\n      <div class=\"content-area\" *ngIf=\"routeLoading\">\n        <div class=\"app-loader\">\n          <span class=\"spinner\"> Loading... </span>\n        </div>\n      </div>\n      <div class=\"content-area\" *ngIf=\"!routeLoading\">\n        <div class=\"page-area\">\n          <router-outlet></router-outlet>\n        </div>\n      </div>\n      <clr-vertical-nav *ngIf=\"IS_LOGEDIN\">\n        <a clrVerticalNavLink routerLink=\"./group\" routerLinkActive=\"active\">\n          <clr-icon clrVerticalNavIcon shape=\"blocks-group\"></clr-icon>\n          Custom Groups\n        </a>\n        <a clrVerticalNavLink routerLink=\"./capacity\" routerLinkActive=\"active\">\n          <clr-icon clrVerticalNavIcon shape=\"resource-pool\"></clr-icon>\n          Capacity\n        </a>\n        <a clrVerticalNavLink routerLink=\"./hierarchy\" routerLinkActive=\"active\">\n          <clr-icon clrVerticalNavIcon shape=\"organization\"></clr-icon>\n          Hierarchy\n        </a>\n        <a clrVerticalNavLink routerLink=\"./network\" routerLinkActive=\"active\">\n          <clr-icon clrVerticalNavIcon shape=\"network-globe\"></clr-icon>\n          Interactions\n        </a>\n        <div class=\"nav-divider\"></div>\n        <a clrVerticalNavLink href=\"https://github.com/vmware/purser\" routerLinkActive=\"active\">\n          <img src=\"../assets/images/GitHub-Mark-32px.png\" alt=\"GitHub\" width=\"15\" height=\"15\">\n          GitHub\n        </a>\n        <a clrVerticalNavLink href=\"https://github.com/vmware/purser/issues/new\" routerLinkActive=\"active\">\n          <clr-icon shape=\"plus-circle\"></clr-icon>\n          New Issue\n        </a>\n      </clr-vertical-nav>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "ui/src/app/app.component.scss",
    "content": ".main-container{\n    .appHeader{\n        font-size: 20px;\n        align-items: center;\n    }\n}\n.content-container {\n    position: relative;\n    height: 100%;\n    display: flex;\n    display: -webkit-flex;\n    display: -moz-flex;\n    display: -ms-flex;\n    flex-direction: column;\n    -webkit-box-direction: normal;\n    -webkit-box-orient: vertical;\n    .header {\n        -webkit-box-flex: 0;\n        box-flex: 0;\n        flex: 0 0 60px;\n        display: flex;\n    }\n    .webpageSpinner {\n        position: absolute;\n        top: 0;\n        bottom: 0;\n        right: 0;\n        left: 0;\n        z-index: 100;\n        background: white;\n        .spinner {\n            position: absolute;\n            margin: auto;\n            top: 0;\n            bottom: 0;\n            right: 0;\n            left: 0;\n        }\n    }\n    .main-body {\n        display: flex;\n        display: -webkit-flex;\n        display: -moz-flex;\n        display: -ms-flex;\n        overflow-x: hidden;\n        -webkit-box-flex: 1;\n        -ms-flex: 1 1 auto;\n        flex: 1 1 auto;\n        .navigation-area {\n            /* -webkit-box-flex: 0;\n            -ms-flex: 0 0 auto;\n            flex: 0 0 auto;\n            -webkit-box-ordinal-group: 0;\n            order: -1;\n            overflow: hidden;\n            display: flex;\n            -webkit-box-orient: vertical;\n            -webkit-box-direction: normal;\n            flex-direction: column;*/\n            background-color: #eee;\n        }\n        .content-area {\n            background-color: #FAFAFA;\n            display: flex;\n            display: -webkit-flex;\n            display: -moz-flex;\n            display: -ms-flex;\n            -webkit-box-flex: 1;\n            -ms-flex: 1 1 auto;\n            flex: 1 1 auto;\n            -webkit-flex-direction: column;\n            flex-direction: column;\n            overflow-x: hidden;\n            padding: 20px 24px 80px 24px;\n            .bread-crumb {\n                border-style: solid;\n                border-width: 0px;\n                border-color: grey;\n                max-height: 40px;\n                font-size: 12px;\n                z-index: 10;\n                .synctime-div {\n                    float: right;\n                    font-size: 12px;\n                }\n            }\n            .page-area {\n                flex: auto;\n                position: relative;\n            }\n            .app-loader {\n                height: 100%;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                flex-direction: column;\n            }\n        }\n    }\n}"
  },
  {
    "path": "ui/src/app/app.component.spec.ts",
    "content": "import { TestBed, async } from '@angular/core/testing';\nimport { AppComponent } from './app.component';\ndescribe('AppComponent', () => {\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [\n        AppComponent\n      ],\n    }).compileComponents();\n  }));\n  it('should create the app', async(() => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.debugElement.componentInstance;\n    expect(app).toBeTruthy();\n  }));\n  it(`should have as title 'purser'`, async(() => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.debugElement.componentInstance;\n    expect(app.title).toEqual('purser');\n  }));\n  it('should render title in a h1 tag', async(() => {\n    const fixture = TestBed.createComponent(AppComponent);\n    fixture.detectChanges();\n    const compiled = fixture.debugElement.nativeElement;\n    expect(compiled.querySelector('h1').textContent).toContain('Welcome to purser!');\n  }));\n});\n"
  },
  {
    "path": "ui/src/app/app.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router, RouterEvent } from '@angular/router';\nimport { MCommon } from './common/messages/common.messages';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.scss']\n})\nexport class AppComponent implements OnInit {\n\n  public routeLoading: boolean = false;\n  public messages: any = {};\n  public IS_LOGEDIN = true;\n\n  constructor(public router: Router) {\n    this.messages = {\n      'common': MCommon\n    }\n  }\n\n  private loadApp() {\n    this.router.events.subscribe((event: RouterEvent) => {\n      this.navigationEventHandler(event);\n    });\n  }\n\n  private navigationEventHandler(event: RouterEvent): void {\n    if (event instanceof NavigationStart) {\n      this.routeLoading = true;\n    }\n    if (event instanceof NavigationEnd) {\n      this.routeLoading = false;\n    }\n\n    // Set loading state to false in both of the below events to hide the spinner in case a request fails.\n    if (event instanceof NavigationCancel) {\n      this.routeLoading = false;\n    }\n    if (event instanceof NavigationError) {\n      this.routeLoading = false;\n    }\n  }\n\n  ngOnInit() {\n    this.loadApp();\n  }\n\n}\n"
  },
  {
    "path": "ui/src/app/app.constants.ts",
    "content": "const BACKEND_BASE_URL = \"http://10.112.141.194\";\nexport const BACKEND_URL = BACKEND_BASE_URL + '/api/'\nexport const BACKEND_AUTH_URL = BACKEND_BASE_URL + '/auth/'\n"
  },
  {
    "path": "ui/src/app/app.module.ts",
    "content": "import { HttpClientModule } from '@angular/common/http';\nimport { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterModule } from '@angular/router';\nimport { ClarityModule } from '@clr/angular';\nimport { GoogleChartsModule } from 'angular-google-charts';\nimport { CookieService } from 'ngx-cookie-service';\nimport { AppComponent } from './app.component';\nimport { ROUTING } from \"./app.routing\";\nimport { CapacityGraphModule } from './modules/capacity-graph/capacity-graph.module';\nimport { ChangepasswordModule } from './modules/changepassword/changepassword.module';\nimport { LogicalGroupModule } from './modules/logical-group/logical-group.module';\nimport { LoginModule } from './modules/login/login.module';\nimport { LogoutModule } from './modules/logout/logout.module';\nimport { OptionsModule } from './modules/options/options.module';\nimport { TopoGraphModule } from './modules/topo-graph/modules';\nimport { TopologyGraphModule } from './modules/topologyGraph/modules';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n  ],\n  imports: [\n    BrowserModule,\n    ClarityModule,\n    BrowserAnimationsModule,\n    RouterModule,\n    HttpClientModule,\n    ROUTING,\n    CapacityGraphModule,\n    TopologyGraphModule,\n    TopoGraphModule,\n    LoginModule,\n    LogoutModule,\n    LogicalGroupModule,\n    ChangepasswordModule,\n    OptionsModule,\n    GoogleChartsModule.forRoot()\n  ],\n  providers: [CookieService],\n  schemas: [CUSTOM_ELEMENTS_SCHEMA],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "ui/src/app/app.routing.ts",
    "content": "/*Framework imports, 3rd party imports */\nimport { ModuleWithProviders } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { LogicalGroupComponent } from './modules/logical-group/components/logical-group.component'\nimport { TopologyGraphComponent } from './modules/topologyGraph/components/topologyGraph.component'\nimport { TopoGraphComponent } from './modules/topo-graph/components/topo-graph.component'\nimport { CapactiyGraphComponent } from './modules/capacity-graph/components/capactiy-graph.component'\nimport { OptionsComponent } from './modules/options/components/options.component'\nimport { ChangepasswordComponent } from './modules/changepassword/components/changepassword.component'\n\nexport const ROUTES: Routes = [\n    { path: 'group', component: LogicalGroupComponent },\n    { path: 'network', component: TopologyGraphComponent },\n    { path: 'hierarchy', component: TopoGraphComponent },\n    { path: 'capacity', component: CapactiyGraphComponent },\n    { path: 'changepassword', component: ChangepasswordComponent },\n    { path: 'options', component: OptionsComponent },\n    { path: '**', redirectTo: 'group', pathMatch: 'full' }\n];\n\nexport const ROUTING: ModuleWithProviders = RouterModule.forRoot(ROUTES);"
  },
  {
    "path": "ui/src/app/common/messages/common.messages.ts",
    "content": "export const MCommon: any = Object.freeze({\n    appHeader: 'PURSER'\n});"
  },
  {
    "path": "ui/src/app/common/messages/left-navigation.messages.ts",
    "content": "export const MLeftNav: any = Object.freeze({\n    homeText: 'Home'\n});"
  },
  {
    "path": "ui/src/app/modules/capacity-graph/capacity-graph.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { GoogleChartsModule } from 'angular-google-charts';\nimport { CapactiyGraphComponent } from './components/capactiy-graph.component';\nimport { CapacityGraphService } from './services/capacity-graph.service';\n\n\n@NgModule({\n  imports: [\n    CommonModule, ClarityModule, FormsModule, GoogleChartsModule\n  ],\n  exports: [CapactiyGraphComponent],\n  declarations: [CapactiyGraphComponent],\n  providers: [CapacityGraphService],\n  schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class CapacityGraphModule { }\n"
  },
  {
    "path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.html",
    "content": "<div class=\"row\">\n    <div class=\"col-xs-12\">\n        <div class=\"card\">\n            <div class=\"card-block graphCardBlock\">\n                <div class=\"headerBlock\">\n                    <div class=\"card-title\">\n                        <span class=\"headerText\">Capacity</span>\n                    </div>\n                    <div class=\"toggleDiv\">\n                        <div class=\"form-group\">\n                            <label class=\"viewSwitchLeftLabel\" for=\"formGroupExampleInput\">Logical View</label>\n                            <div class=\"toggle-switch right-label\">\n                                <input type=\"checkbox\" id=\"viewSwitch\" [(ngModel)]=\"physicalView\" (ngModelChange)=\"viewChange()\">\n                                <label for=\"viewSwitch\">Physical View</label>\n                            </div>\n                        </div>\n                    </div>\n                    <div class=\"resetBtn\">\n                        <button class=\"btn btn-outline btn-sm\" (click)=\"reset()\">Reset</button>\n                    </div>\n                </div>\n                <div *ngIf=\"CAPA_STATUS === 'READY'\">\n                    <div class=\"metricDiv\">\n                        <div class=\"selectDiv\">\n                            <div class=\"selectMetricDiv\">\n                                <clr-radio-container clrInline class=\"selectOptions\">\n                                    <div>\n                                        <label>Select Metric</label>\n                                    </div>\n                                    <clr-radio-wrapper *ngFor=\"let item of metricOptions\" class=\"radioWrapper\">\n                                        <input type=\"radio\" clrRadio name=\"options\" required value=\"{{item.value}}\" [(ngModel)]=\"selectedMetric\" (ngModelChange)=\"metricChange($event)\"\n                                         style=\"cursor:pointer\" />\n                                        <label class=\"radioLabel\">{{item.displayValue}}</label>\n                                    </clr-radio-wrapper>\n                                </clr-radio-container>\n                            </div>\n                            <div class=\"selectDropdownDiv\">\n                                <clr-select-container>\n                                    <div>\n                                        <label>Filter</label>\n                                    </div>\n                                    <select clrSelect name=\"filerItems\" [(ngModel)]=\"selectedFilterItem\" (ngModelChange)=\"filterItemChange($event)\" style=\"cursor:pointer\">\n                                        <option value=\"select\">--Select--</option>\n                                        <option *ngFor=\"let item of filterItems\">{{item}}</option>\n                                    </select>\n                                </clr-select-container>\n                            </div>\n                        </div>\n                        <div class=\"rootDataDiv\">\n                            <table class=\"table\">\n                                <thead>\n                                    <tr>\n                                        <th class=\"left\">Name</th>\n                                        <th class=\"left\">Type</th>\n                                        <th class=\"right\" *ngFor=\"let item of metricOptions\">{{item.displayValue + (item.units ? '('+item.units+')' : '')}}</th>\n                                        <th class=\"right\" *ngFor=\"let item of metricOptions\">{{item.displayValue + ' Cost ($)'}}</th>\n                                        <th class=\"right\">Total Cost ($)</th>\n                                    </tr>\n                                </thead>\n                                <tbody>\n                                    <tr>\n                                        <td class=\"left\">{{rootItem.name || '-'}}</td>\n                                        <td class=\"left\">{{rootItem.type || '-'}}</td>\n                                        <td class=\"right\" *ngFor=\"let item of metricOptions\">{{rootItem[item.value] | number: '1.0-2' || 0}}</td>\n                                        <td class=\"right\" *ngFor=\"let item of metricOptions\">{{rootItem[item.value+'Cost'] | number: '1.0-2' || 0}}</td>\n                                        <td class=\"right\">{{rootItem['cpuCost'] + rootItem['memoryCost'] + rootItem['storageCost'] | number: '1.0-2' || 0}}</td>\n                                    </tr>\n                                </tbody>\n                            </table>\n                        </div>\n                    </div>\n                    <div class=\"googleChartDiv\">\n                        <google-chart class=\"googleChart\" [type]=\"'TreeMap'\" [data]=\"graphData\" [columnNames]=\"colNames\" [options]=\"chartOptions\"\n                            (select)=\"onSelect($event)\" style=\"cursor:pointer\"></google-chart>\n                    </div>\n                </div>\n                <p class=\"card-text\" *ngIf=\"CAPA_STATUS === 'WAIT'\">\n                        <span class=\"spinner\"></span>\n                </p>\n                <div *ngIf=\"CAPA_STATUS === 'NO_DATA'\">\n                    <div class=\"clr-row\">\n                        <div class=\"clr-col-4\">\n                            <div class=\"alert alert-warning\" role=\"alert\">\n                                <div class=\"alert-items\">\n                                    <div class=\"alert-item static\">\n                                        <div class=\"alert-icon-wrapper\">\n                                            <clr-icon class=\"alert-icon\" shape=\"exclamation-circle\"></clr-icon>\n                                        </div>\n                                        <span class=\"alert-text\">\n                                            No data found.\n                                        </span>\n                                    </div>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<div class=\"row\" style=\"margin-top: 5%\" *ngIf=\"CAPA_STATUS === 'READY'\">\n    <div class=\"headerBlock\">\n        <div class=\"card-title\">\n            <span class=\"headerText\">{{resourceType + ' Resource Allocation vs Capacity'}}</span>\n        </div>\n    </div>\n</div>\n\n<div class=\"clr-row\" *ngIf=\"CAPA_STATUS === 'READY'\">\n    <div class=\"clr-col-12 clr-col-sm-4\">\n            <div class=\"card\">\n                <div class=\"card-block\">\n                    <h4 class=\"card-title\">CPU</h4>\n                    <p class=\"card-text\">Allocated: {{ cpuAllocated }} vCPU<br />  Capacity: {{ cpuCapacity }} vCPU</p>\n                    <div class=\"progress-block\">\n                        <label>CPU</label>\n                        <div class=\"progress-static labeled\">\n                            <div class=\"progress-meter\" [attr.data-value]=\"cpuRatio\" [attr.data-displayval]=\"cpuRatio + '%'\"></div>\n                            <span>{{ cpuRatio }}%</span>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n    <div class=\"clr-col-12 clr-col-sm-4\">\n        <div class=\"card\">\n            <div class=\"card-block\">\n                <h4 class=\"card-title\">Memory</h4>\n                <p class=\"card-text\">Allocated: {{ memoryAllocated }} GB<br />  Capacity: {{ memoryCapacity }} GB</p>\n                <div class=\"progress-block\">\n                    <label>Memory</label>\n                    <div class=\"progress-static labeled\">\n                        <div class=\"progress-meter\" [attr.data-value]=\"memoryRatio\" [attr.data-displayval]=\"memoryRatio + '%'\"></div>\n                        <span>{{ memoryRatio }}%</span>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n    <div class=\"clr-col-12 clr-col-sm-4\">\n        <div class=\"card\">\n            <div class=\"card-block\">\n                <h4 class=\"card-title\">Storage</h4>\n                <p class=\"card-text\">Allocated: {{ storageAllocated }} GB<br />  Capacity: {{ storageCapacity }} GB</p>\n                <div class=\"progress-block\">\n                    <label>Storage</label>\n                    <div class=\"progress-static labeled\">\n                        <div class=\"progress-meter\" [attr.data-value]=\"storageRatio\" [attr.data-displayval]=\"storageRatio + '%'\"></div>\n                        <span>{{ storageRatio }}%</span>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.scss",
    "content": ".graphCardBlock{\n    ::ng-deep .googleChart{\n        width: 100%;\n    }\n    .headerBlock{\n        display: flex;\n        .headerText{\n            font-size: 18px;\n        }\n        .card-title{\n            flex: 1;\n        }\n        .toggleDiv{\n            .viewSwitchLeftLabel{\n                padding-right: 5px;\n            }\n        }\n    }\n    .card-text{\n        text-align: center;\n    }\n    .radioWrapper{\n        padding: 5px;\n        .radioLabel{\n            padding-left: 5px;\n        }\n    }\n    .googleChartDiv{\n        padding-top: 10px;\n    }\n    .selectDiv{\n        display: flex;\n        .selectDropdownDiv{\n            padding-left: 60px;\n        }\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { CapactiyGraphComponent } from './capactiy-graph.component';\n\ndescribe('CapactiyGraphComponent', () => {\n  let component: CapactiyGraphComponent;\n  let fixture: ComponentFixture<CapactiyGraphComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ CapactiyGraphComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CapactiyGraphComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.ts",
    "content": "import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { CapacityGraphService } from '../services/capacity-graph.service';\n\nconst STATUS_WAIT = 'WAIT',\n    STATUS_READY = 'READY',\n    STATUS_NODATA = 'NO_DATA';\n\n@Component({\n    selector: 'app-capactiy-graph',\n    templateUrl: './capactiy-graph.component.html',\n    styleUrls: ['./capactiy-graph.component.scss']\n})\n\n\nexport class CapactiyGraphComponent implements OnInit {\n    public cpuAllocated = 100.0;\n    public cpuCapacity = 100.0;\n    public cpuRatio = 100;\n    public memoryAllocated = 100.0;\n    public memoryCapacity = 100.0;\n    public memoryRatio = 100;\n    public storageAllocated = 100.0;\n    public storageCapacity = 100.0;\n    public storageRatio = 100;\n    public resourceType = 'Cluster';\n\n    //PUBLIC\n    public CAPA_STATUS = STATUS_WAIT;\n    public graphData = [];\n    public colNames = ['Child', 'Parent', 'Metrics'];\n    public chartOptions = {\n        nodeClass: 'customNode',\n        allowHtml: true,\n        animation: {\n            startup: true,\n            duration: 1000,\n            easing: 'out',\n        },\n        minColor: '#009688',\n        midColor: '#f7f7f7',\n        maxColor: '#ee8100',\n        headerHeight: 40,\n    };\n    public selectedMetric: string = 'cpu';\n    public metricOptions: any = [\n        { displayValue: 'CPU', value: 'cpu', units: 'vCPU' },\n        { displayValue: 'Memory', value: 'memory', units: 'GB' },\n        { displayValue: 'Storage', value: 'storage', units: 'GB' }\n        //{ displayValue: 'Network', value: 'network' }\n    ];\n    public physicalView: boolean = false;\n    public rootItem: any = {};\n    public filterItems: any = [];\n    public selectedFilterItem: string = 'select';\n\n    //PRIVATE\n    private orgCapaData: any = {};\n    private capaData: any = {};\n    private keysToConsider: any = ['service', 'pod', 'container', 'process', 'cluster', 'namespace', 'deployment', 'replicaset', 'node', 'daemonset', 'job', 'statefulset', 'children', 'pv', 'pvc'];\n    private uniqNames: any = [];\n\n    constructor(private router: Router, private capacityGraphService: CapacityGraphService) { }\n\n    private getCapacityData() {\n        let observableEntity: Observable<any> = this.capacityGraphService.getCapacityData(this.physicalView);\n        this.CAPA_STATUS = STATUS_WAIT;\n        observableEntity.subscribe((response) => {\n            if (!response) {\n                return;\n            }\n            this.capaData = response && response.data || {};\n            this.orgCapaData = JSON.parse(JSON.stringify(this.capaData));\n\n            this.constructData(this.capaData);\n        }, (err) => {\n            this.CAPA_STATUS = STATUS_NODATA;\n        });\n    }\n\n    private computeAllocationRatios(data) {\n        if (!!data.cpuCapacity) {\n            this.cpuCapacity = data.cpuCapacity.toFixed(2);\n        } else {\n            this.cpuCapacity = 0;\n        }\n        if (!!data.cpuAllocated) {\n            this.cpuAllocated = data.cpuAllocated.toFixed(2);\n        } else {\n            this.cpuAllocated = 0;\n        }\n        this.cpuRatio = Math.round(this.cpuAllocated * 100 / this.cpuCapacity);\n\n        if (!!data.memoryCapacity) {\n            this.memoryCapacity = data.memoryCapacity.toFixed(2);\n        } else {\n            this.memoryCapacity = 0;\n        }\n        if (!!data.memoryAllocated) {\n            this.memoryAllocated = data.memoryAllocated.toFixed(2);\n        } else {\n            this.memoryAllocated = 0;\n        }\n        this.memoryRatio = Math.round(this.memoryAllocated * 100 / this.memoryCapacity);\n\n        if (!!data.storageCapacity) {\n            this.storageCapacity = data.storageCapacity.toFixed(2);\n        } else {\n            this.storageCapacity = 0;\n        }\n        if (!!data.storageAllocated) {\n            this.storageAllocated = data.storageAllocated.toFixed(2);\n        } else {\n            this.storageAllocated = 0;\n        }\n        this.storageRatio = Math.round(this.storageAllocated * 100 / this.storageCapacity);\n\n        if (data.type == 'node') {\n            this.resourceType = 'Node';\n            this.storageAllocated = 0;\n            this.storageCapacity = 0;\n            this.storageRatio = 0;\n        } else {\n            if (data.type == 'pv') {\n                this.resourceType = 'PersistentVolume';\n                this.cpuAllocated = 0;\n                this.cpuCapacity = 0;\n                this.cpuRatio = 0;\n                this.memoryAllocated = 0;\n                this.memoryCapacity = 0;\n                this.memoryRatio = 0;\n            } else {\n                this.resourceType = 'Cluster';\n            }\n        }\n    }\n\n    private constructRoot(capaData) {\n        this.rootItem = capaData;\n        let eachRow = [];\n        let rootName = capaData && capaData.name;\n        let metricValue = capaData[this.selectedMetric] || 0;\n        let metricCostValue = capaData[this.selectedMetric + 'Cost'] || 0;\n        if (rootName) {\n            eachRow.push({ v: rootName, f: rootName + ', ' + this.selectedMetric + ': ' + metricValue.toFixed(2) + ', ' + this.selectedMetric + ' cost: ' + metricCostValue.toFixed(2) });\n            eachRow.push(null);\n            eachRow.push(0);\n            if (this.uniqNames.indexOf(rootName) === -1) {\n                this.graphData.push(eachRow);\n                this.uniqNames.push(rootName);\n            }\n        }\n    }\n\n    private pushToGraphData(item, parent) {\n        let eachRow = [];\n        let parentName = item.name === parent.name ? parent.type : parent.name;\n        let metricValue = item[this.selectedMetric] || 0;\n        let metricCostValue = item[this.selectedMetric + 'Cost'] || 0;\n        eachRow.push({ v: item.name, f: item.name + ', ' + this.selectedMetric + ': ' + metricValue.toFixed(2) + ', ' + this.selectedMetric + ' cost: ' + metricCostValue.toFixed(2), t: item.type });\n        eachRow.push(parentName);\n        eachRow.push(metricValue);\n        if (this.uniqNames.indexOf(item.name) === -1) {\n            this.graphData.push(eachRow);\n            this.uniqNames.push(item.name);\n        }\n    }\n\n    private collectFilterItems(item) {\n        this.filterItems.push(item.name);\n    }\n\n    private constructData(capaData) {\n        this.selectedFilterItem = 'select';\n        this.graphData = [];\n        this.uniqNames = [];\n        this.constructRoot(capaData);\n        let data = JSON.parse(JSON.stringify(capaData));\n        for (let key in data) {\n            if (this.keysToConsider.indexOf(key) > -1) {\n                this.filterItems = [];\n                for (let item of data[key]) {\n                    this.collectFilterItems(item);\n                    this.pushToGraphData(item, data);\n                }\n            }\n        }\n\n        this.computeAllocationRatios(data);\n\n        this.CAPA_STATUS = STATUS_READY;\n        //console.log(this.graphData);\n    }\n\n    public onSelect(element) {\n        if (!element) {\n            return;\n        }\n        if (!element[0]) {\n            return;\n        }\n        let row = element[0].row;\n        let selectedItem = this.graphData[row];\n        this.getAdditionalData(selectedItem);\n    }\n\n    private getAdditionalData(item) {\n        let selectedItem = item;\n        if (item && item[0] && item[0].v && item[0].t) {\n            let name = item[0].v;\n            let type = item[0].t;\n            let observableEntity: Observable<any> = this.capacityGraphService.getCapacityData(this.physicalView, type, name);\n            this.CAPA_STATUS = STATUS_WAIT;\n            observableEntity.subscribe((response) => {\n                if (!response) {\n                    return;\n                }\n                let capaData = response && response.data || {};\n                this.constructData(capaData);\n            }, (err) => {\n                this.CAPA_STATUS = STATUS_NODATA;\n            });\n        } else {\n            return;\n        }\n    }\n\n    public filterItemChange(evt) {\n        for (let item of this.graphData) {\n            for (let subItem of item) {\n                if (subItem && subItem.v && subItem.v === this.selectedFilterItem) {\n                    this.getAdditionalData(item);\n                }\n            }\n        }\n    }\n\n    public metricChange(evt) {\n        this.constructData(this.orgCapaData);\n    }\n\n    public reset() {\n        this.CAPA_STATUS = STATUS_WAIT;\n        this.graphData = [];\n        this.uniqNames = [];\n        this.constructData(this.orgCapaData);\n    }\n\n    public viewChange() {\n        this.getCapacityData()\n    }\n\n    ngOnInit() {\n        this.getCapacityData();\n    }\n\n}\n"
  },
  {
    "path": "ui/src/app/modules/capacity-graph/services/capacity-graph.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_URL } from '../../../app.constants';\n\n@Injectable()\nexport class CapacityGraphService {\n    constructor(private http: HttpClient) {\n\n    }\n\n    public getCapacityData(view?, type?, name?) {\n        let _devUrl: string = './json/capacity.json';\n        let _url: string = BACKEND_URL + 'metrics';\n\n        if (type) {\n            _url = _url + '/' + type;\n        }\n\n        if (view && !name) {\n            _url = _url + '?view=physical';\n        }\n\n        if (name) {\n            _url = _url + '?name=' + name;\n            _devUrl = './json/capacity1.json'; //testing purpose\n        }\n\n        //console.log(_url);\n\n        return this.http.get(_url, {\n            observe: 'body',\n            responseType: 'json',\n            withCredentials: true,\n        });\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/changepassword/changepassword.module.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { ChangepasswordComponent } from './components/changepassword.component';\nimport { ChangepasswordService } from './services/changepassword.service';\n\n\n@NgModule({\n    imports: [\n        CommonModule, ClarityModule, FormsModule\n    ],\n    exports: [ChangepasswordComponent],\n    declarations: [ChangepasswordComponent],\n    providers: [ChangepasswordService],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class ChangepasswordModule { }"
  },
  {
    "path": "ui/src/app/modules/changepassword/components/changepassword.component.html",
    "content": "<div class=\"login-wrapper\">\n    <form class=\"login\">\n        <section class=\"title\">\n            <h3 class=\"welcome\">Welcome to</h3>\n            Purser\n        </section>\n        <div class=\"login-group\">\n            <clr-input-container>\n                <input type=\"text\" name=\"username\" clrInput placeholder=\"Username\" [(ngModel)]=\"form.username\"/>\n            </clr-input-container>\n            <clr-password-container>\n                <input type=\"password\" name=\"password\" clrPassword placeholder=\"OldPassword\" [(ngModel)]=\"form.password\"/>\n            </clr-password-container>\n            <clr-password-container>\n                <input type=\"password\" name=\"password\" clrPassword placeholder=\"NewPassword\" [(ngModel)]=\"form.newPassword\"/>\n            </clr-password-container>\n            <clr-password-container>\n                <input type=\"password\" name=\"password\" clrPassword placeholder=\"NewPassword\" [(ngModel)]=\"form.newPasswordCheck\"/>\n            </clr-password-container>\n            <div class=\"error active\" *ngIf=\"notMatch\">\n                New passwords didn't match\n            </div>\n            <div class=\"error active\" *ngIf=\"LOGIN_STATUS == 'wrong'\">\n                Invalid user name or password\n            </div>\n            <div class=\"alert alert-success\" role=\"alert\" *ngIf=\"LOGIN_STATUS == 'success'\">\n                <div class=\"alert-items\">\n                    <div class=\"alert-item static\">\n                        <div class=\"alert-icon-wrapper\">\n                            <clr-icon class=\"alert-icon\" shape=\"check-circle\"></clr-icon>\n                        </div>\n                        <span class=\"alert-text\">Password Change Success</span>\n                    </div>\n                </div>\n            </div>\n            <button [clrLoading]=\"submitBtnState\" type=\"submit\" class=\"btn btn-primary\" (click)=\"submitChangePassword()\">Login</button>\n        </div>\n    </form>\n</div>"
  },
  {
    "path": "ui/src/app/modules/changepassword/components/changepassword.component.scss",
    "content": ""
  },
  {
    "path": "ui/src/app/modules/changepassword/components/changepassword.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { ChangepasswordComponent } from './changepassword.component';\n\ndescribe('CapactiyGraphComponent', () => {\n  let component: ChangepasswordComponent;\n  let fixture: ComponentFixture<ChangepasswordComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ ChangepasswordComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(ChangepasswordComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/changepassword/components/changepassword.component.ts",
    "content": "import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\nimport { HttpClient } from  \"@angular/common/http\";\nimport { Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { ChangepasswordService } from '../services/changepassword.service';\n\n@Component({\n    selector: 'app-changepassword',\n    templateUrl: './changepassword.component.html',\n    styleUrls: ['./changepassword.component.scss']\n})\nexport class ChangepasswordComponent implements OnInit {\n    public form: any = {};\n    public notMatch = false;\n    public LOGIN_STATUS = \"wait\";\n    ngOnInit() {\n        this.notMatch = false;\n        this.LOGIN_STATUS = \"wait\";\n    }\n\n    constructor(private router: Router, private changepasswordService: ChangepasswordService) { }\n\n    public submitChangePassword() {\n        if (this.form.newPassword != this.form.newPasswordCheck) {\n            this.notMatch = true;\n            return;\n        }\n        this.notMatch = false;\n        var credentials = JSON.stringify(this.form);\n        let observableEntity: Observable<any> = this.changepasswordService.sendLoginCredentials(credentials);\n        observableEntity.subscribe((response) => {\n            this.LOGIN_STATUS = \"success\";\n        }, (err) => {\n            this.LOGIN_STATUS = \"wrong\";\n        });\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/changepassword/services/changepassword.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_AUTH_URL } from '../../../app.constants';\n\n@Injectable()\nexport class ChangepasswordService {\n    url: string;\n    constructor(private http: HttpClient) {\n        this.url = BACKEND_AUTH_URL + 'changePassword';\n    }\n\n    public sendLoginCredentials(credentials) {\n        return this.http.post(this.url, credentials);\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/logical-group/components/logical-group.component.css",
    "content": ".header-wrapper {\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n  margin-bottom: 15px;\n}\n\n.quick-filters {\n  font-weight: bold;\n}\n\n::ng-deep .datagrid-overlay-wrapper {\n  overflow-x: hidden;\n}"
  },
  {
    "path": "ui/src/app/modules/logical-group/components/logical-group.component.html",
    "content": "<div class=\"header-wrapper\">\n  <h3>Custom Groups</h3>\n  <div>\n    <button [clrLoading]=\"submitBtnState\" type=\"submit\" class=\"btn btn-primary-outline\" (click)=\"fillGroupData()\">\n      <clr-icon shape=\"plus\"></clr-icon> ADD GROUP\n    </button>\n  </div>\n</div>\n\n<div>\n  <span class=\"quick-filters\">Quick Filters: </span>\n  <button type=\"button\" (click)=\"changeCurrentMonthView()\" class=\"label clickable {{ getCurrentMonthStatus() }}\"\n    style=\"cursor:pointer;font-size: 10pt;\">\n    Current Month Cost\n  </button>\n  <button type=\"button\" (click)=\"changeLastMonthView()\" class=\"label clickable {{ getLastMonthStatus() }}\"\n    style=\"cursor:pointer;font-size: 10pt;\">\n    Last Month Cost\n  </button>\n  <button type=\"button\" (click)=\"changeLast3MonthView()\" class=\"label clickable {{ getLast3MonthStatus() }}\"\n    style=\"cursor:pointer;font-size: 10pt;\">\n    3 Months Cost\n  </button>\n  <button type=\"button\" (click)=\"changeProjectedMonthView()\" class=\"label clickable {{ getProjectedMonthStatus() }}\"\n    style=\"cursor:pointer;font-size: 10pt;\">\n    Projected Cost\n  </button>\n</div>\n\n<clr-datagrid>\n  <clr-dg-column [clrDgField]=\"'name'\">Group Name</clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'podsCount'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: true}\">Pods</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'cpu'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: false}\">CPU (vCPU)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'memory'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: false}\">Memory (GB)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'storage'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: false}\">Storage (GB)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'mtdCPU'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewCurrentMonth}\">CPU Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'mtdMemory'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewCurrentMonth}\">Memory Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'mtdStorage'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewCurrentMonth}\">Storage Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'mtdCost'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewCurrentMonth}\">Total Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'projectedCPU'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewProjected}\">Projected CPU Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'projectedMemory'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewProjected}\">Projected Memory Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'projectedStorage'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewProjected}\">Projected Storage Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'projectedCost'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewProjected}\">Projected Cost (US $)</ng-container>\n  </clr-dg-column>\n\n  <clr-dg-column [clrDgField]=\"'lastMonthCPU'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastMonth}\">Last Month CPU Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'lastMonthMemory'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastMonth}\">Last Month Memory Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'lastMonthStorage'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastMonth}\">Last Month Storage Cost (US $)</ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'lastMonthCost'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastMonth}\">Last Month Cost (US $)</ng-container>\n  </clr-dg-column>\n\n  <clr-dg-column [clrDgField]=\"'lastThreeMonthsCPU'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastThreeMonth}\">Last three months CPU Cost (US $)\n    </ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'lastThreeMonthsMemory'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastThreeMonth}\">Last three months Memory Cost (US $)\n    </ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'lastThreeMonthsStorage'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastThreeMonth}\">Last three months Storage Cost (US $)\n    </ng-container>\n  </clr-dg-column>\n  <clr-dg-column [clrDgField]=\"'lastThreeMonthsCost'\">\n    <ng-container *clrDgHideableColumn=\"{hidden: !isViewLastThreeMonth}\">Last three months Cost (US $)\n    </ng-container>\n  </clr-dg-column>\n\n\n  <clr-dg-placeholder>We couldn't find any custom groups!</clr-dg-placeholder>\n\n  <clr-dg-row *clrDgItems=\"let group of groups\">\n    <clr-dg-cell><button class=\"btn btn-sm btn-link\" style=\"padding:-4%; margin:-4%; text-align: left\"\n        (click)=\"showGroupDetails(group)\">{{ group.name }}</button></clr-dg-cell>\n    <clr-dg-cell>{{ group.podsCount }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.cpu | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.memory | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.storage | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.mtdCPUCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.mtdMemoryCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.mtdStorageCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.mtdCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.projectedCPUCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.projectedMemoryCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.projectedStorageCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.projectedCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n\n    <clr-dg-cell>{{ (group.lastMonthCPUCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.lastMonthMemoryCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.lastMonthStorageCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n    <clr-dg-cell>{{ (group.lastMonthCost | number: '1.0-2') || 0 }}</clr-dg-cell>\n\n    <clr-dg-cell>\n      {{ (((group.mtdCPUCost || 0) + (group.lastMonthCPUCost || 0) + (group.lastLastMonthCPUCost || 0)) | number: '1.0-2') || 0 }}\n    </clr-dg-cell>\n    <clr-dg-cell>\n      {{ (((group.mtdMemoryCost || 0) + (group.lastMonthMemoryCost || 0) + (group.lastLastMonthMemoryCost || 0))| number: '1.0-2') || 0 }}\n    </clr-dg-cell>\n    <clr-dg-cell>\n      {{ (((group.mtdStorageCost || 0) + (group.lastMonthStorageCost || 0) + (group.lastLastMonthStorageCost || 0)) | number: '1.0-2') || 0 }}\n    </clr-dg-cell>\n    <clr-dg-cell>\n      {{ (((group.mtdCost || 0) + (group.lastMonthCost || 0) + (group.lastLastMonthCost || 0)) | number: '1.0-2') || 0 }}\n    </clr-dg-cell>\n    <clr-dg-action-overflow>\n      <button class=\"action-item\" (click)=\"setToBeDeletedGroup(group.name)\">\n        <clr-icon shape=\"trash\"></clr-icon> DELETE\n      </button>\n    </clr-dg-action-overflow>\n  </clr-dg-row>\n\n  <clr-dg-footer>\n    <clr-dg-column-toggle>\n      <clr-dg-column-toggle-title>Hide/Show Features</clr-dg-column-toggle-title>\n      <clr-dg-column-toggle-button>Select All</clr-dg-column-toggle-button>\n    </clr-dg-column-toggle>\n    {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}\n    of {{pagination.totalItems}} groups\n    <clr-dg-pagination #pagination [clrDgPageSize]=\"currentPageSize\"></clr-dg-pagination>\n  </clr-dg-footer>\n</clr-datagrid>\n\n<form novalidate #f=\"ngForm\" (ngSubmit)=\"createGroup();\">\n  <clr-modal [(clrModalOpen)]=\"isCreateGroup\" [clrModalSize]=\"'lg'\">\n    <h3 class=\"modal-title\">New Custom Group</h3>\n    <div class=\"modal-body\">\n      <form clrForm>\n        <clr-textarea-container>\n          <textarea clrTextarea [(ngModel)]=\"group\" name=\"group\" placeholder=\"Group Definition in JSON or YAML\"\n            rows=\"15\" required></textarea>\n        </clr-textarea-container>\n      </form>\n\n      <p class=\"p5\">For group template click <a\n          href=\"https://github.com/vmware/purser/blob/master/cluster/artifacts/group-template.json\">here</a>.<br />\n        To read more about groups check <a\n          href=\"https://github.com/vmware/purser/blob/master/docs/custom-group-installation-and-usage.md\">this</a>.\n      </p>\n    </div>\n    <div class=\"modal-footer\">\n      <button type=\"button\" class=\"btn btn-outline\" (click)=\"isCreateGroup = false\">Cancel</button>\n      <button type=\"button\" class=\"btn btn-primary\" (click)=\"createGroup()\">Create</button>\n    </div>\n  </clr-modal>\n</form>\n\n<clr-modal [(clrModalOpen)]=\"isDeleteGroup\">\n  <h3 class=\"modal-title\">Delete group {{ toBeDeletedGroup }}?</h3>\n  <div class=\"modal-footer\">\n    <button type=\"button\" class=\"btn btn-outline\" (click)=\"isDeleteGroup = false\">Cancel</button>\n    <button type=\"button\" class=\"btn btn-danger\" (click)=\"deleteGroup()\">Delete</button>\n  </div>\n</clr-modal>\n\n<div *ngIf=\"groupCreation === 'fail'\">\n  <div class=\"clr-row\">\n    <div class=\"clr-col-8\">\n      <clr-alert clrAlertType=\"danger\" [clrAlertClosable]=\"true\">\n        <clr-alert-item>\n          <div class=\"alert-item static\">\n            <span class=\"alert-text\">\n              Group creation failed<br /><br />\n              Error: {{ creationError }}\n            </span>\n          </div>\n        </clr-alert-item>\n      </clr-alert>\n    </div>\n  </div>\n</div>\n\n<div *ngIf=\"groupCreation === 'success'\">\n  <div class=\"clr-row\">\n    <div class=\"clr-col-4\">\n      <clr-alert clrAlertType=\"success\" [clrAlertClosable]=\"true\">\n        <clr-alert-item>\n          <div class=\"alert-item static\">\n            <span class=\"alert-text\">\n              Successfully created group.\n            </span>\n          </div>\n        </clr-alert-item>\n      </clr-alert>\n    </div>\n  </div>\n</div>\n\n<div *ngIf=\"groupDeletion === 'success'\">\n  <div class=\"clr-row\">\n    <div class=\"clr-col-4\">\n      <clr-alert clrAlertType=\"success\" [clrAlertClosable]=\"true\">\n        <clr-alert-item>\n          <div class=\"alert-item static\">\n            <span class=\"alert-text\">\n              Successfully deleted group.\n            </span>\n          </div>\n        </clr-alert-item>\n      </clr-alert>\n    </div>\n  </div>\n</div>\n\n<div *ngIf=\"groupDeletion === 'fail'\">\n  <div class=\"clr-row\">\n    <div class=\"clr-col-8\">\n      <clr-alert clrAlertType=\"danger\" [clrAlertClosable]=\"true\">\n        <clr-alert-item>\n          <div class=\"alert-item static\">\n            <span class=\"alert-text\">\n              Group deletion failed<br /><br />\n              Error: {{ deletionError }}\n            </span>\n          </div>\n        </clr-alert-item>\n      </clr-alert>\n    </div>\n  </div>\n</div>\n\n<div class=\"clr-row\" *ngIf=\"isShowGroupDetails\">\n  <div class=\"clr-col-lg-12 clr-col-md-12 clr-col-12\">\n    <div class=\"card\">\n      <div class=\"card-header\">\n        Resource Details for <b style=\"text-transform: uppercase;\">{{ groupToFocus.name }}</b>\n      </div>\n      <div class=\"card-block\">\n        <div class=\"card-text\">\n          <div class=\"row\">\n            <div class=\"clr-col-4\">\n              <b>Pods</b> : {{ groupToFocus.podsCount }}<br />\n              <b>CPU</b> : {{ groupToFocus.cpu }}<br />\n              <b>Memory</b> : {{ groupToFocus.memory }}<br />\n              <b>Storage</b> : {{ groupToFocus.storage }}<br />\n              <b>CurrentMonth Cost</b> : {{ groupToFocus.mtdCost }}<br />\n              <b>Last Month Cost</b> : {{ groupToFocus.lastMonthCost || 0 }}<br />\n              <b>Last 3 Months Cost</b> :\n              {{ (groupToFocus.mtdCost || 0) + (groupToFocus.lastMonthCost || 0) + (groupToFocus.lastLastMonthCost || 0) }}\n            </div>\n            <div class=\"clr-col-2\"></div>\n            <div class=\"clr-col-4 progress-block\">\n              <label>Current Cost vs Projected</label>\n              <div class=\"progress-static labeled\">\n                <div class=\"progress-meter\" [attr.data-value]=\"costRatio\" [attr.data-displayval]=\"costRatio + '%'\">\n                </div>\n              </div>\n              <span style=\"margin: 1%\">{{ costRatio }}%</span>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"card-footer\">\n        <button class=\"btn btn-sm btn-link\" (click)=\"showMTD()\">MTD Costs</button>\n        <button class=\"btn btn-sm btn-link\" (click)=\"showProjected()\">Projected Costs</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"clr-row\" *ngIf=\"isShowMTD\">\n  <div class=\"card\">\n    <div class=\"card-header\">\n      MTD Costs\n    </div>\n    <div class=\"card-block\">\n      <div class=\"googleChartDiv\" *ngIf=\"isShowMTD\">\n        <google-chart class=\"googleChart\" [type]=\"'PieChart'\" [data]=\"donutData.data\" [options]=\"donutOptions\"\n          [dynamicResize]=\"true\"></google-chart>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"clr-row\" *ngIf=\"isShowProjected\">\n  <div class=\"card\">\n    <div class=\"card-header\">\n      Projected Costs\n    </div>\n    <div class=\"card-block\">\n      <div class=\"googleChartDiv\" *ngIf=\"isShowProjected\">\n        <google-chart class=\"googleChart\" [type]=\"'PieChart'\" [data]=\"donutData.data\" [options]=\"donutOptions\"\n          [dynamicResize]=\"true\"></google-chart>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "ui/src/app/modules/logical-group/components/logical-group.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LogicalGroupComponent } from './logical-group.component';\n\ndescribe('LogicalGroupComponent', () => {\n  let component: LogicalGroupComponent;\n  let fixture: ComponentFixture<LogicalGroupComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LogicalGroupComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LogicalGroupComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/logical-group/components/logical-group.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { LogicalGroupService } from '../services/logical-group.service';\n\nconst STATUS_WAIT = 'WAIT',\n  STATUS_READY = 'READY',\n  STATUS_NODATA = 'NO_DATA';\n\n@Component({\n  selector: 'app-logical-group',\n  templateUrl: './logical-group.component.html',\n  styleUrls: ['./logical-group.component.css']\n})\nexport class LogicalGroupComponent implements OnInit {\n  public groups: Object[];\n  public GROUP_STATUS = STATUS_WAIT;\n  public isCreateGroup = false;\n  public isDeleteGroup = false;\n  public isShowGroupDetails = false;\n  public toBeDeletedGroup = \"Custom Group\";\n  public groupToFocus: any;\n  public groupCreation = 'wait';\n  public groupDeletion = 'wait';\n  public creationError = null;\n  public deletionError = null;\n\n  public isShowMTD = false;\n  public isShowProjected = false;\n  public donutOptions = {};\n  public donutData = { \"data\": [] };\n  public group: any;\n  public costRatio = 100;\n\n  public isViewCurrentMonth = true;\n  public isViewLastMonth = false;\n  public isViewLastThreeMonth = false;\n  public isViewProjected = false;\n\n  constructor(private router: Router, private logicalGroupService: LogicalGroupService) {\n  }\n\n  public getCurrentMonthStatus() {\n    if (this.isViewCurrentMonth) {\n      return 'label-info'\n    } else {\n      return 'label-purple'\n    }\n  }\n\n  public getLastMonthStatus() {\n    if (this.isViewLastMonth) {\n      return 'label-info'\n    } else {\n      return 'label-purple'\n    }\n  }\n\n  public getLast3MonthStatus() {\n    if (this.isViewLastThreeMonth) {\n      return 'label-info'\n    } else {\n      return 'label-purple'\n    }\n  }\n\n  public getProjectedMonthStatus() {\n    if (this.isViewProjected) {\n      return 'label-info'\n    } else {\n      return 'label-purple'\n    }\n  }\n\n  public changeCurrentMonthView() {\n    this.isViewCurrentMonth = !this.isViewCurrentMonth;\n  }\n\n  public changeLastMonthView() {\n    this.isViewLastMonth = !this.isViewLastMonth;\n  }\n\n  public changeLast3MonthView() {\n    this.isViewLastThreeMonth = !this.isViewLastThreeMonth;\n  }\n\n  public changeProjectedMonthView() {\n    this.isViewProjected = !this.isViewProjected;\n  }\n\n\n  private getLogicalGroupData() {\n    let observableEntity: Observable<any> = this.logicalGroupService.getLogicalGroupData();\n    this.GROUP_STATUS = STATUS_WAIT;\n    observableEntity.subscribe((response) => {\n      if (!response) {\n        console.log(\"empty response\")\n        return;\n      }\n      this.groups = JSON.parse(JSON.stringify(response));\n    }, (err) => {\n      this.GROUP_STATUS = STATUS_NODATA;\n    });\n  }\n\n  public fillGroupData() {\n    this.isCreateGroup = true;\n    this.group = null;\n  }\n\n  public deleteGroupData() {\n    this.toBeDeletedGroup = \"Custom Group\";\n    this.isDeleteGroup = true;\n  }\n\n  public createGroup() {\n    let observableEntity: Observable<any> = this.logicalGroupService.createCustomGroup(this.group);\n    observableEntity.subscribe((response) => {\n      console.log(\"successfully created group\");\n      this.groupCreation = 'success';\n      setTimeout(() => this.ngOnInit(), 6000);\n    }, (err) => {\n      console.log(\"failed to create group\", err);\n      this.groupCreation = 'fail';\n      this.creationError = err[\"error\"];\n      ;\n    });\n    this.isCreateGroup = false;\n  }\n\n  public deleteGroup() {\n    console.log(\"deleting group \", this.toBeDeletedGroup)\n    let observableEntity: Observable<any> = this.logicalGroupService.deleteCustomGroup(this.toBeDeletedGroup);\n    observableEntity.subscribe((response) => {\n      console.log(\"successfully deleted group\");\n      this.groupDeletion = 'success';\n      setTimeout(() => this.ngOnInit(), 6000);\n    }, (err) => {\n      console.log(\"failed to delete group\", err);\n      this.groupDeletion = 'fail';\n      this.deletionError = err[\"error\"];\n    });\n    this.isDeleteGroup = false;\n  }\n\n  public setToBeDeletedGroup(grpName) {\n    this.toBeDeletedGroup = grpName;\n    this.isDeleteGroup = true;\n  }\n\n  public showGroupDetails(group) {\n    console.log(\"group: \", group);\n    this.groupToFocus = group;\n    this.isShowGroupDetails = true;\n    this.costRatio = Math.round(this.groupToFocus.mtdCost * 100 / this.groupToFocus.projectedCost);\n  }\n\n  public reset() {\n    this.isCreateGroup = false;\n    this.getLogicalGroupData();\n    this.isDeleteGroup = false;\n    this.isShowGroupDetails = false;\n    this.toBeDeletedGroup = \"Custom Group\";\n    this.group = null;\n    this.groupCreation = 'wait';\n    this.groupDeletion = 'wait';\n  }\n\n  public showMTD() {\n    this.isShowMTD = true;\n    this.isShowProjected = false;\n    this.donutData = {\n      \"data\": [\n        ['CPU', this.groupToFocus.mtdCPUCost],\n        ['Memory', this.groupToFocus.mtdMemoryCost],\n        ['Storage', this.groupToFocus.mtdStorageCost]\n      ]\n    };\n\n    this.donutOptions = {\n      title: 'Total MTD Cost for ' + this.groupToFocus.name + ': ' + this.groupToFocus.mtdCost.toFixed(2),\n      pieHole: 0.3,\n      pieSliceText: 'value-and-percentage',\n      width: 750,\n      height: 400,\n      chartArea: {\n        left: \"10%\",\n        top: \"10%\",\n        height: \"80%\",\n        width: \"80%\"\n      }\n    };\n  }\n\n  public showProjected() {\n    this.isShowProjected = true;\n    this.isShowMTD = false;\n    this.donutData = {\n      \"data\": [\n        ['CPU', this.groupToFocus.projectedCPUCost],\n        ['Memory', this.groupToFocus.projectedMemoryCost],\n        ['Storage', this.groupToFocus.projectedStorageCost]\n      ]\n    };\n\n    this.donutOptions = {\n      title: 'Total Projected Cost for ' + this.groupToFocus.name + ': ' + this.groupToFocus.projectedCost.toFixed(2),\n      pieHole: 0.3,\n      pieSliceText: 'value-and-percentage',\n      width: 750,\n      height: 400,\n      chartArea: {\n        left: \"10%\",\n        top: \"10%\",\n        height: \"80%\",\n        width: \"80%\"\n      }\n    };\n  }\n\n  ngOnInit() {\n    this.reset();\n  }\n\n}\n"
  },
  {
    "path": "ui/src/app/modules/logical-group/logical-group.module.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { GoogleChartsModule } from 'angular-google-charts';\nimport { LogicalGroupComponent } from './components/logical-group.component';\nimport { LogicalGroupService } from './services/logical-group.service';\n\n\n@NgModule({\n    imports: [\n        CommonModule, ClarityModule, FormsModule, GoogleChartsModule\n    ],\n    exports: [LogicalGroupComponent],\n    declarations: [LogicalGroupComponent],\n    providers: [LogicalGroupService],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class LogicalGroupModule { }\n"
  },
  {
    "path": "ui/src/app/modules/logical-group/services/logical-group.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_URL } from '../../../app.constants';\n\n@Injectable()\nexport class LogicalGroupService {\n    constructor(private http: HttpClient) {\n\n    }\n\n    public getLogicalGroupData(name?) {\n        let _devUrl: string = './json/logicalGroup.json';\n        let _url: string = BACKEND_URL + 'groups';\n\n        if (name) {\n            _url = _url + '?name=' + name;\n            _devUrl = './json/logicalGroup1.json'; //testing purpose\n        }\n\n        //console.log(_url);\n\n        return this.http.get(_url, {\n            observe: 'body',\n            responseType: 'json',\n            withCredentials: true,\n        });\n    }\n\n    public deleteCustomGroup(name) {\n        let _url: string = BACKEND_URL + 'group/delete?name=' + name;\n        return this.http.post(_url, null, { withCredentials: true })\n    }\n\n    public createCustomGroup(groupDef) {\n        let _url: string = BACKEND_URL + 'group/create';\n        return this.http.post(_url, groupDef, { withCredentials: true })\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/login/components/login.component.html",
    "content": "<div class=\"login-wrapper\">\n    <form class=\"login\">\n        <section class=\"title\">\n            <h3 class=\"welcome\">Welcome to</h3>\n            Purser\n        </section>\n        <div class=\"login-group\">\n            <clr-input-container>\n                <input type=\"text\" name=\"username\" clrInput placeholder=\"Username\" [(ngModel)]=\"form.username\"/>\n            </clr-input-container>\n            <clr-password-container>\n                <input type=\"password\" name=\"password\" clrPassword placeholder=\"Password\" [(ngModel)]=\"form.password\"/>\n            </clr-password-container>\n            <div class=\"error active\" *ngIf=\"LOGIN_STATUS == 'wrong'\">\n                Invalid user name or password\n            </div>\n            <div class=\"alert alert-success\" role=\"alert\" *ngIf=\"LOGIN_STATUS == 'success'\">\n                <div class=\"alert-items\">\n                    <div class=\"alert-item static\">\n                        <div class=\"alert-icon-wrapper\">\n                            <clr-icon class=\"alert-icon\" shape=\"check-circle\"></clr-icon>\n                        </div>\n                        <span class=\"alert-text\">Login Success</span>\n                    </div>\n                </div>\n            </div>\n            <button [clrLoading]=\"submitBtnState\" type=\"submit\" class=\"btn btn-primary\" (click)=\"submitLogin()\" (keyup.enter)=\"submitLogin()\">Login</button>\n        </div>\n    </form>\n</div>"
  },
  {
    "path": "ui/src/app/modules/login/components/login.component.scss",
    "content": ""
  },
  {
    "path": "ui/src/app/modules/login/components/login.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginComponent } from './login.component';\n\ndescribe('LoginComponent', () => {\n  let component: LoginComponent;\n  let fixture: ComponentFixture<LoginComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/login/components/login.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AppComponent } from '../../../app.component';\nimport { LoginService } from '../services/login.service';\n\n@Component({\n  selector: 'app-login',\n  templateUrl: './login.component.html',\n  styleUrls: ['./login.component.scss']\n})\nexport class LoginComponent implements OnInit {\n  public form: any = {};\n  public LOGIN_STATUS = \"wait\";\n  ngOnInit() {\n    this.LOGIN_STATUS = \"wait\";\n    this.appComponent.IS_LOGEDIN = false;\n  }\n\n  constructor(private router: Router, private loginService: LoginService, private appComponent: AppComponent) { }\n\n  public submitLogin() {\n    var credentials = JSON.stringify(this.form);\n    let observableEntity: Observable<any> = this.loginService.sendLoginCredential(credentials);\n    observableEntity.subscribe((response) => {\n      this.LOGIN_STATUS = \"success\";\n      this.appComponent.IS_LOGEDIN = true;\n      this.router.navigateByUrl('/group');\n    }, (err) => {\n      this.LOGIN_STATUS = \"wrong\";\n      this.appComponent.IS_LOGEDIN = false;\n    });\n  }\n}"
  },
  {
    "path": "ui/src/app/modules/login/login.module.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { LoginComponent } from './components/login.component';\nimport { LoginService } from './services/login.service';\n\n\n@NgModule({\n    imports: [\n        CommonModule, ClarityModule, FormsModule\n    ],\n    exports: [LoginComponent],\n    declarations: [LoginComponent],\n    providers: [LoginService],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class LoginModule { }"
  },
  {
    "path": "ui/src/app/modules/login/services/login.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_AUTH_URL } from '../../../app.constants';\n\n@Injectable()\nexport class LoginService {\n    url: string;\n    private sessionID: string;\n    constructor(private http: HttpClient) {\n        this.url = BACKEND_AUTH_URL + 'login';\n    }\n\n    public sendLoginCredential(credentials) {\n        const httpPostOptions =\n        {\n            withCredentials: true,\n        }\n        return this.http.post(this.url, credentials, httpPostOptions);\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/logout/components/logout.component.html",
    "content": "<p>Logging out</p>"
  },
  {
    "path": "ui/src/app/modules/logout/components/logout.component.scss",
    "content": ""
  },
  {
    "path": "ui/src/app/modules/logout/components/logout.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LogoutComponent } from './logout.component';\n\ndescribe('LogoutComponent', () => {\n  let component: LogoutComponent;\n  let fixture: ComponentFixture<LogoutComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LogoutComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LogoutComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/logout/components/logout.component.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { AppComponent } from '../../../app.component';\nimport { BACKEND_AUTH_URL } from '../../../app.constants';\n\n@Component({\n    selector: 'app-logout',\n    templateUrl: './logout.component.html',\n    styleUrls: ['./logout.component.scss']\n})\nexport class LogoutComponent implements OnInit {\n    public form: any = {};\n    public LOGIN_STATUS = \"wait\";\n    ngOnInit() {\n        this.handleLogout();\n        this.LOGIN_STATUS = \"wait\";\n    }\n\n    constructor(private router: Router, private http: HttpClient, private appComponent: AppComponent) { }\n\n    public handleLogout() {\n        let logoutURL = BACKEND_AUTH_URL + 'logout';\n        const logoutOptions = {\n            withCredentials: true\n        };\n        this.http.post(logoutURL, JSON.stringify({}), logoutOptions).subscribe((response) => {\n            this.appComponent.IS_LOGEDIN = false;\n        }, (err) => {\n            console.log(\"Error\", err);\n        }\n        );\n        this.router.navigateByUrl(\"./login\");\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/logout/logout.module.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { LogoutComponent } from './components/logout.component';\n\n\n@NgModule({\n    imports: [\n        CommonModule, ClarityModule, FormsModule\n    ],\n    exports: [LogoutComponent],\n    declarations: [LogoutComponent],\n    providers: [],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class LogoutModule { }"
  },
  {
    "path": "ui/src/app/modules/options/components/options.component.html",
    "content": "<button type=\"button\" (click)=\"initiateSync()\" class=\"btn\">\n    Sync\n</button>"
  },
  {
    "path": "ui/src/app/modules/options/components/options.component.scss",
    "content": ""
  },
  {
    "path": "ui/src/app/modules/options/components/options.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { OptionsComponent } from './options.component';\n\ndescribe('OptionsComponent', () => {\n  let component: OptionsComponent;\n  let fixture: ComponentFixture<OptionsComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ OptionsComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(OptionsComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/options/components/options.component.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit } from '@angular/core';\nimport { BACKEND_URL } from '../../../app.constants';\n\n@Component({\n  selector: 'app-options',\n  templateUrl: './options.component.html',\n  styleUrls: ['./options.component.scss']\n})\nexport class OptionsComponent implements OnInit {\n  public SYNC_STATUS = \"wait\";\n  ngOnInit() {\n    this.SYNC_STATUS = \"wait\";\n  }\n\n  constructor(private http: HttpClient) { }\n\n  public initiateSync() {\n    let syncURL = BACKEND_URL + 'sync';\n    const syncOptions = {\n      withCredentials: true\n    };\n    this.http.get(syncURL, syncOptions).subscribe((response) => {\n      this.SYNC_STATUS = \"requested\";\n      console.log(\"sync status\", this.SYNC_STATUS);\n    }, (err) => {\n      console.log(\"Error\", err);\n      this.SYNC_STATUS = \"failed\";\n      console.log(\"sync request status\", this.SYNC_STATUS);\n    });\n  }\n\n}"
  },
  {
    "path": "ui/src/app/modules/options/options.module.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { OptionsComponent } from './components/options.component';\n\n\n@NgModule({\n    imports: [\n        CommonModule, ClarityModule, FormsModule\n    ],\n    exports: [OptionsComponent],\n    declarations: [OptionsComponent],\n    providers: [],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class OptionsModule { }"
  },
  {
    "path": "ui/src/app/modules/topo-graph/components/topo-graph.component.html",
    "content": "<div class=\"row\">\n    <div class=\"col-xs-12\">\n        <div class=\"card\">\n            <div class=\"card-block graphCardBlock\">\n                <div class=\"headerBlock\">\n                    <div class=\"card-title\">\n                        <span class=\"headerText\">Hierarchy</span>\n                    </div>\n                    <div class=\"filterDiv\">\n                        <clr-select-container>\n                            <label>Filter</label>\n                            <select clrSelect name=\"filerItems\" [(ngModel)]=\"selectedFilterItem\" (ngModelChange)=\"filterItemChange($event)\">\n                                <option value=\"select\">--Select--</option>\n                                <option *ngFor=\"let item of filterItems\">{{item}}</option>\n                            </select>\n                        </clr-select-container>\n                    </div>\n                    <div class=\"toggleDiv\">\n                        <div class=\"form-group\">\n                            <label class=\"viewSwitchLeftLabel\" for=\"formGroupExampleInput\">Logical View</label>\n                            <div class=\"toggle-switch right-label\">\n                                <input type=\"checkbox\" id=\"viewSwitch\" [(ngModel)]=\"physicalView\" (ngModelChange)=\"viewChange()\">\n                                <label for=\"viewSwitch\">Physical View</label>\n                            </div>\n                        </div>\n                    </div>\n                    <div class=\"resetBtn\">\n                        <button class=\"btn btn-outline btn-sm\" (click)=\"reset()\">Reset</button>\n                    </div>\n                </div>\n                <div class=\"card-text\" *ngIf=\"TOPO_STATUS === 'READY'\">\n                    <div class=\"legendDiv\">\n                        <div class=\"legend\" *ngFor=\"let item of legendArr\">\n                            <div class=\"fakeLegend\" [ngStyle]=\"{'background-color':item.color}\"></div>\n                            <div class=\"fakeLegendText\">{{item.displayText}}</div>\n                        </div>\n                    </div>\n                    <google-chart class=\"googleChart\" [type]=\"'OrgChart'\" [data]=\"graphData\" [columnNames]=\"colNames\" [options]=\"chartOptions\"\n                        (select)=\"onSelect($event)\" style=\"cursor:pointer\"></google-chart>\n                </div>\n                <p class=\"card-text\" *ngIf=\"TOPO_STATUS === 'WAIT'\">\n                    <span class=\"spinner\"></span>\n                </p>\n                <div *ngIf=\"TOPO_STATUS === 'NO_DATA'\">\n                    <div class=\"clr-row\">\n                        <div class=\"clr-col-4\">\n                            <div class=\"alert alert-warning\" role=\"alert\">\n                                <div class=\"alert-items\">\n                                    <div class=\"alert-item static\">\n                                        <div class=\"alert-icon-wrapper\">\n                                            <clr-icon class=\"alert-icon\" shape=\"exclamation-circle\"></clr-icon>\n                                        </div>\n                                        <span class=\"alert-text\">\n                                            No data found.\n                                        </span>\n                                    </div>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "ui/src/app/modules/topo-graph/components/topo-graph.component.scss",
    "content": ".graphCardBlock{\n    ::ng-deep .googleChart{\n        display: block;\n        margin: 0 auto;\n    }\n    ::ng-deep .customNode{\n        border: 1px solid #2B7CE9;\n        border-radius: 5%;\n        background-color: whitesmoke;\n        font-size: 14px;\n        font-weight: 800;\n    }\n    .headerBlock{\n        display: flex;\n        .headerText{\n            font-size: 18px;\n        }\n        .card-title{\n            flex: 1;\n        }\n        .filterDiv{\n            label{\n                padding-right: 10px;\n            }\n            padding-right: 60px;\n        }\n        .toggleDiv{\n            .viewSwitchLeftLabel{\n                padding-right: 5px;\n            }\n        }\n    }\n    .card-text{\n        text-align: center;\n        overflow-x: auto;\n        .legendDiv{\n            display: flex;\n            .legend{\n                display: flex;\n                align-items: center;\n                padding: 5px;\n                .fakeLegend{\n                    width: 10px;\n                    height: 10px;\n                    border-radius: 50%;\n                }\n                .fakeLegendText{\n                    padding-left: 5px;\n                }\n            }\n        }\n    }\n    ::ng-deep .namespace{\n        color: red;\n    }\n    ::ng-deep .service{\n        color: yellow;\n    }\n    ::ng-deep .pod{\n        color: green;\n    }\n    ::ng-deep .container{\n        color: blue\n    }\n    ::ng-deep .process{\n        color: orange;\n    }\n    ::ng-deep .cluster{\n        color: orangered;\n    }\n    ::ng-deep .deployment{\n        color: purple;\n    }\n    ::ng-deep .replicaset{\n        color: palevioletred;\n    }\n    ::ng-deep .node{\n        color: royalblue;\n    }\n    ::ng-deep .daemonset{\n        color: brown;\n    }\n    ::ng-deep .job{\n        color: black;\n    }\n    ::ng-deep .statefulset{\n        color: goldenrod;\n    }\n}\n"
  },
  {
    "path": "ui/src/app/modules/topo-graph/components/topo-graph.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { TopoGraphComponent } from './topo-graph.component';\n\ndescribe('TopoGraphComponent', () => {\n  let component: TopoGraphComponent;\n  let fixture: ComponentFixture<TopoGraphComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ TopoGraphComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(TopoGraphComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "ui/src/app/modules/topo-graph/components/topo-graph.component.ts",
    "content": "import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { TopoGraphService } from '../services/topo-graph.service';\n\nconst STATUS_WAIT = 'WAIT',\n    STATUS_READY = 'READY',\n    STATUS_NODATA = 'NO_DATA';\n\n@Component({\n    selector: 'app-topo-graph',\n    templateUrl: './topo-graph.component.html',\n    styleUrls: ['./topo-graph.component.scss']\n})\nexport class TopoGraphComponent implements OnInit {\n    //PUBLIC\n    public TOPO_STATUS = STATUS_WAIT;\n    public graphData = [];\n    public colNames = ['Child', 'Parent', 'Tooltip']\n    public chartOptions = {\n        nodeClass: 'customNode',\n        allowHtml: true\n    };\n    public physicalView: boolean = false;\n    public legendArr: any = [\n        {\n            displayText: 'namespace',\n            color: 'red'\n        },\n        {\n            displayText: 'service',\n            color: 'yellow'\n        },\n        {\n            displayText: 'pod',\n            color: 'green'\n        },\n        {\n            displayText: 'container',\n            color: 'blue'\n        },\n        {\n            displayText: 'process',\n            color: 'orange'\n        },\n        {\n            displayText: 'cluster',\n            color: 'orangered'\n        },\n        {\n            displayText: 'deployment',\n            color: 'purple'\n        },\n        {\n            displayText: 'replicaset',\n            color: 'palevioletred'\n        },\n        {\n            displayText: 'node',\n            color: 'royalblue'\n        },\n        {\n            displayText: 'daemonset',\n            color: 'brown'\n        },\n        {\n            displayText: 'job',\n            color: 'black'\n        },\n        {\n            displayText: 'statefulset',\n            color: 'goldenrod'\n        }\n    ];\n    public filterItems: any = [];\n    public selectedFilterItem: string = 'select';\n\n    //PRIVATE\n    private orgTopoData: any = {};\n    private topoData: any = {};\n    private keysToConsider: any = ['service', 'pod', 'container', 'process', 'cluster', 'namespace', 'deployment', 'replicaset', 'node', 'daemonset', 'job', 'statefulset', 'children'];\n    private uniqNames: any = [];\n\n    constructor(private router: Router, private topoService: TopoGraphService) { }\n\n    private getTopoData() {\n        this.graphData = [];\n        this.uniqNames = [];\n        let observableEntity: Observable<any> = this.topoService.getTopoData(this.physicalView);\n        this.TOPO_STATUS = STATUS_WAIT;\n        observableEntity.subscribe((response) => {\n            if (!response) {\n                return;\n            }\n            this.topoData = response && response.data || {};\n            this.orgTopoData = JSON.parse(JSON.stringify(this.topoData));\n            this.constructData(this.topoData);\n            //console.log(this.topoData);\n        }, (err) => {\n            this.TOPO_STATUS = STATUS_NODATA;\n        });\n    }\n\n    private getIcon(type) {\n        if (!type) {\n            return 'host';\n        }\n        switch (type) {\n            case 'service':\n                return 'cluster';\n            case 'pod':\n                return 'storage';\n            case 'container':\n                return 'host';\n            default:\n                return 'host';\n\n        }\n    }\n\n    private pushToGraphData(item, parent) {\n        let eachRow = [];\n        let iconShape = this.getIcon(item.type);\n        let parentName = item.name === parent.name ? parent.type : parent.name;\n        eachRow.push({ v: item.name, f: '<span class=\"' + item.type + '\">' + item.name + '</span>', t: item.type });\n        eachRow.push(parentName);\n        eachRow.push(item.type);\n        if (this.uniqNames.indexOf(item.name) === -1) {\n            this.graphData.push(eachRow);\n            this.uniqNames.push(item.name);\n        }\n    }\n\n    /*private traverse(data){\n        for (let key in data) {\n            if (this.keysToConsider.indexOf(key) > -1) {\n                for (let item of data[key]) {\n                    this.pushToGraphData(item, item);\n                }\n            }\n        }\n    }*/\n\n    private collectFilterItems(item) {\n        this.filterItems.push(item.name);\n    }\n\n    private constructData(topoData) {\n        let data = JSON.parse(JSON.stringify(topoData));\n        for (let key in data) {\n            if (this.keysToConsider.indexOf(key) > -1) {\n                this.filterItems = [];\n                for (let item of data[key]) {\n                    this.collectFilterItems(item);\n                    this.pushToGraphData(item, data);\n                    /*for (let subKey in item) {\n                        if (this.keysToConsider.indexOf(subKey) > -1) {\n                            for (let subItem of item[subKey]) {\n                                this.pushToGraphData(subItem, item);\n                                for (let supSubKey in subItem) {\n                                    if (this.keysToConsider.indexOf(supSubKey) > -1) {\n                                        for (let supSubItem of subItem[supSubKey]) {\n                                            this.pushToGraphData(supSubItem, subItem);\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }*/\n                }\n            }\n        }\n        this.TOPO_STATUS = STATUS_READY;\n        //console.log(this.graphData);\n    }\n\n    public onSelect(element) {\n        if (!element) {\n            return;\n        }\n        if (!element[0]) {\n            return;\n        }\n        let row = element[0].row;\n        let selectedItem = this.graphData[row];\n        this.getAdditionalData(selectedItem);\n    }\n\n    private getAdditionalData(item) {\n        this.selectedFilterItem = 'select';\n        if (item && item[0] && item[0].v && item[0].t) {\n            let name = item[0].v;\n            let type = item[0].t;\n            let observableEntity: Observable<any> = this.topoService.getTopoData(this.physicalView, type, name);\n            this.TOPO_STATUS = STATUS_WAIT;\n            observableEntity.subscribe((response) => {\n                if (!response) {\n                    return;\n                }\n                let topoData = response && response.data || {};\n                this.constructData(topoData);\n            }, (err) => {\n                this.TOPO_STATUS = STATUS_NODATA;\n            });\n        } else {\n            return;\n        }\n\n    }\n\n    public filterItemChange(evt) {\n        for (let item of this.graphData) {\n            for (let subItem of item) {\n                if (subItem && subItem.v && subItem.v === this.selectedFilterItem) {\n                    this.uniqNames = [];\n                    this.graphData = [];\n                    this.getAdditionalData(item);\n                }\n            }\n        }\n    }\n\n    public reset() {\n        this.TOPO_STATUS = STATUS_WAIT;\n        this.graphData = [];\n        this.uniqNames = [];\n        this.constructData(this.orgTopoData);\n    }\n\n    public viewChange() {\n        this.getTopoData();\n    }\n\n    ngOnInit() {\n        this.getTopoData();\n    }\n\n}\n"
  },
  {
    "path": "ui/src/app/modules/topo-graph/modules.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { Routes, RouterModule } from '@angular/router';\nimport { TopoGraphComponent } from './components/topo-graph.component';\nimport { TopoGraphService } from './services/topo-graph.service';\nimport { GoogleChartsModule } from 'angular-google-charts';\n\n@NgModule({\n    imports: [RouterModule, CommonModule, ClarityModule, FormsModule, GoogleChartsModule],\n    declarations: [TopoGraphComponent],\n    exports: [TopoGraphComponent],\n    providers: [TopoGraphService],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class TopoGraphModule {\n\n}"
  },
  {
    "path": "ui/src/app/modules/topo-graph/services/topo-graph.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { CookieService } from 'ngx-cookie-service';\nimport { BACKEND_URL } from '../../../app.constants';\n\n@Injectable()\nexport class TopoGraphService {\n    constructor(private http: HttpClient, private cookieService: CookieService) {\n\n    }\n\n    public getTopoData(view?, type?, name?) {\n        let _devUrl: string = './json/topology.json';\n        let _url: string = BACKEND_URL + 'hierarchy';\n\n        if (type) {\n            _url = _url + '/' + type;\n        }\n\n        if (view && !name) {\n            _url = _url + '?view=physical';\n        }\n\n        if (name) {\n            _url = _url + '?name=' + name;\n            _devUrl = './json/topology1.json'; //testing purpose\n        }\n\n        return this.http.get(_url, {\n            observe: 'body',\n            responseType: 'json',\n            withCredentials: true,\n        });\n    }\n}"
  },
  {
    "path": "ui/src/app/modules/topologyGraph/components/index.ts",
    "content": "export * from './topologyGraph.component';"
  },
  {
    "path": "ui/src/app/modules/topologyGraph/components/topologyGraph.component.html",
    "content": "<h3>Interactions</h3>\n<div class=\"actionDiv\">\n    <clr-select-container *ngIf=\"enableClustering\">\n        <label class=\"serviceSelectLabel\">Select service</label>\n        <select clrSelect name=\"options\" [(ngModel)]=\"serviceName\" (change)=\"initNetwork()\" class=\"serviceSelect\">\n            <option value=\"ALL\">ALL</option>\n            <option value=\"{{item}}\" *ngFor=\"let item of serviceList\">{{item}}</option>\n        </select>\n    </clr-select-container>\n    <div class=\"buttonDiv\">\n        <button type=\"button\" (click)=\"reset()\" class=\"btn btn-sm\" *ngIf=\"enableClustering\">Pod View</button>\n        <button type=\"button\" (click)=\"clusterByCid()\" class=\"btn btn-sm\" *ngIf=\"!enableClustering\">Cluster Pods by Services</button>\n    </div>\n</div>\n<div class=\"descDiv\" *ngIf=\"enableClustering\">\n    <span>Scroll in to cluster. Press on the cluster to open up.</span>\n</div>\n<div class=\"spinnerDiv\" *ngIf=\"NODE_STATUS === 'WAIT' || EDGE_STATUS === 'WAIT'\">\n    <div class=\"spinner\">\n    </div>\n</div>\n\n<div class=\"mainDiv\" id=\"mynetwork\" #networkContainer>\n\n<div *ngIf=\"NODE_STATUS === 'NO_DATA' || EDGE_STATUS === 'NO_DATA'\">\n        <div class=\"clr-row\">\n            <div class=\"clr-col-4\">\n                <div class=\"alert alert-warning\" role=\"alert\">\n                    <div class=\"alert-items\">\n                        <div class=\"alert-item static\">\n                            <div class=\"alert-icon-wrapper\">\n                                <clr-icon class=\"alert-icon\" shape=\"exclamation-circle\"></clr-icon>\n                            </div>\n                            <span class=\"alert-text\">\n                                Experimental feature disabled!\n                            </span>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n"
  },
  {
    "path": "ui/src/app/modules/topologyGraph/components/topologyGraph.component.scss",
    "content": ".mainDiv{\n    height: 500px;\n    width: 100%;\n    border: 1px solid #ddd;\n}\n.serviceSelect{\n    width: 30%;\n}\n.serviceSelectLabel{\n    padding-right: 15px;\n}\n.actionDiv{\n    display: flex;\n}\n.buttonDiv{\n    width: 100%;\n    text-align: right;\n}\n.spinnerDiv{\n    width: 100%;\n    text-align: center;\n}"
  },
  {
    "path": "ui/src/app/modules/topologyGraph/components/topologyGraph.component.ts",
    "content": "import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { DataSet, Network } from 'vis';\nimport { Observable } from 'rxjs';\nimport { TopologyGraphService } from '../services/topologyGraph.service';\n\nconst STATUS_WAIT = 'WAIT',\n    STATUS_READY = 'READY',\n    STATUS_NODATA = 'NO_DATA';\n\n@Component({\n    selector: 'topology-graph',\n    templateUrl: './topologyGraph.component.html',\n    styleUrls: ['./topologyGraph.component.scss']\n})\n\nexport class TopologyGraphComponent implements OnInit {\n    private clusterIndex = 0;\n    private clusters = [];\n    private lastClusterZoomLevel = 0;\n    private clusterFactor = 0.9;\n    private nodes: any;\n    private edges: any;\n    private nodesDataset: any;\n    private edgesDataset: any;\n\n    public NODE_STATUS = STATUS_WAIT;\n    public EDGE_STATUS = STATUS_WAIT;\n    public serviceList: any = [];\n    public enableClustering: boolean = false;\n    public serviceName: string = 'ALL';\n\n    @ViewChild('networkContainer') container: ElementRef;\n\n\n    data: any = {};\n    options = {\n        nodes: {\n            shape: 'dot',\n            size: 16\n        },\n        physics: {\n            enabled: false,\n            /*forceAtlas2Based: {\n                gravitationalConstant: -26,\n                centralGravity: 0.005,\n                springLength: 230,\n                springConstant: 0.18\n            },\n            maxVelocity: 146,\n            solver: 'forceAtlas2Based',\n            timestep: 0.35,\n            stabilization: { iterations: 150 }*/\n        },\n        layout: {\n            improvedLayout: false\n        }\n    };\n\n\n    network: any;\n\n    constructor(private router: Router, private topologyService: TopologyGraphService) {\n\n    }\n\n    private getServiceList() {\n        let observableEntity: Observable<any> = this.topologyService.getServiceList();\n        this.NODE_STATUS = STATUS_WAIT;\n        observableEntity.subscribe((response) => {\n            if (!response) {\n                return;\n            }\n            this.serviceList = response;\n        }, (err) => {\n        });\n    }\n\n    private getNodes() {\n        this.serviceList = [];\n        let observableEntity: Observable<any> = this.topologyService.getNodes(this.serviceName);\n        this.NODE_STATUS = STATUS_WAIT;\n        observableEntity.subscribe((response) => {\n            if (!response) {\n                this.NODE_STATUS = STATUS_NODATA;\n                return;\n            }\n            this.nodes = response;\n            for (let item of this.nodes) {\n                if (item.cid && this.serviceList.indexOf(item.cid) === -1) {\n                    for (let cid of item.cid) {\n                        if (this.serviceList.indexOf(cid) === -1) {\n                            this.serviceList.push(cid);\n                        }\n                    }\n                }\n            }\n            this.NODE_STATUS = STATUS_READY;\n            this.initNetwork();\n        }, (err) => {\n            this.NODE_STATUS = STATUS_NODATA;\n        });\n    }\n\n    private getEdges() {\n        let observableEntity: Observable<any> = this.topologyService.getEdges(this.serviceName);\n        this.EDGE_STATUS = STATUS_WAIT;\n        observableEntity.subscribe((response) => {\n            if (!response) {\n                this.EDGE_STATUS = STATUS_NODATA;\n                return;\n            }\n            this.edges = response;\n            this.EDGE_STATUS = STATUS_READY;\n            this.initNetwork();\n        }, (err) => {\n            this.EDGE_STATUS = STATUS_NODATA;\n        });\n    }\n\n    private initNetwork() {\n        let filteredNodes = [];\n        let filteredEdges = [];\n        if (this.EDGE_STATUS === STATUS_READY && this.NODE_STATUS === STATUS_READY) {\n            if (this.serviceName && this.serviceName !== 'ALL') {\n                let self = this;\n                filteredNodes = this.nodes.filter(function (item) {\n                    return item.cid.indexOf(self.serviceName) > -1;\n                });\n                let idsArr = [];\n                for (let item of filteredNodes) {\n                    idsArr.push(item.id);\n                }\n                //console.log(idsArr);\n                filteredEdges = this.edges.filter(function (item) {\n                    return idsArr.indexOf(item.from) > -1 || idsArr.indexOf(item.to) > -1;\n                });\n                let leftOutIdsArr = [];\n                for (let item of filteredEdges) {\n                    if (leftOutIdsArr.indexOf(item.from) === -1) {\n                        leftOutIdsArr.push(item.from);\n                    }\n                    if (leftOutIdsArr.indexOf(item.to) === -1) {\n                        leftOutIdsArr.push(item.to);\n                    }\n                }\n                filteredNodes = this.nodes.filter(function (item) {\n                    return leftOutIdsArr.indexOf(item.id) > -1;\n                })\n            } else {\n                filteredNodes = this.nodes;\n                filteredEdges = this.edges;\n            }\n            //console.log(filteredNodes);\n            //console.log(filteredEdges);\n\n            this.nodesDataset = new DataSet(filteredNodes);\n            this.edgesDataset = new DataSet(filteredEdges);\n            this.data = {\n                nodes: this.nodesDataset,\n                edges: this.edgesDataset\n            };\n            this.network = new Network(this.container.nativeElement, this.data, this.options);\n            this.network.stabilize(100);\n            // if we click on a node, we want to open it up!\n            let self = this;\n            this.network.on(\"selectNode\", function (params) {\n                if (params.nodes.length === 1 && self.network.isCluster(params.nodes[0])) {\n                    self.network.openCluster(params.nodes[0]);\n                }\n            });\n            if (this.serviceName && this.enableClustering) {\n                this.clusterByCid();\n            }\n        }\n    }\n\n    public reset() {\n        this.serviceName = 'ALL';\n        this.enableClustering = false;\n        this.reload();\n    }\n\n    public reload() {\n        this.clusterIndex = 0;\n        this.clusters = [];\n        this.lastClusterZoomLevel = 0;\n        this.clusterFactor = 0.9;\n        this.nodes = []\n        this.edges = []\n        this.nodesDataset = []\n        this.edgesDataset = []\n\n        this.NODE_STATUS = STATUS_WAIT;\n        this.EDGE_STATUS = STATUS_WAIT;\n        this.data = {};\n        this.options = {\n            nodes: {\n                shape: 'dot',\n                size: 16\n            },\n            physics: {\n                enabled: false,\n                /*forceAtlas2Based: {\n                    gravitationalConstant: -26,\n                    centralGravity: 0.005,\n                    springLength: 230,\n                    springConstant: 0.18\n                },\n                maxVelocity: 146,\n                solver: 'forceAtlas2Based',\n                timestep: 0.35,\n                stabilization: { iterations: 150 }*/\n            },\n            layout: {\n                improvedLayout: false\n            }\n        };\n        this.network = {};\n        this.loadApp();\n    }\n\n    private loadApp() {\n        this.getNodes();\n        this.getEdges();\n    }\n\n    ngOnInit() {\n        //this.getServiceList();\n        this.loadApp();\n    }\n\n    public clusterByCid() {\n        this.enableClustering = true;\n        this.network.setData(this.data);\n        let nodeServices = [];\n        for (let item of this.nodes) {\n            for (let cid of item.cid) {\n                if (cid && nodeServices.indexOf(cid) === -1) {\n                    nodeServices.push(cid);\n                }\n            }\n        }\n        /*for (let i = 0; i < this.nodes.length; i++) {\n            nodeServices[i] = (this.nodes[i].cid);\n        }*/\n        let uniqServices = ([...nodeServices]);\n        let clusterOptionsByData = new Array(uniqServices.length);\n        for (let i = 0; i < uniqServices.length; i++) {\n            clusterOptionsByData[i] = {\n                joinCondition: function (childOptions) {\n                    //return childOptions.cid == uniqServices[i];\n                    return childOptions.cid && childOptions.cid.indexOf(uniqServices[i]) > -1;\n                },\n                clusterNodeProperties: { allowSingleNodeCluster: true, id: 'cidCluster' + i, borderWidth: 2, shape: 'star', label: uniqServices[i] }\n            };\n            //console.log(clusterOptionsByData[i]);\n            this.network.cluster(clusterOptionsByData[i]);\n        }\n    }\n\n}\n"
  },
  {
    "path": "ui/src/app/modules/topologyGraph/modules.ts",
    "content": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { ClarityModule } from '@clr/angular';\nimport { Routes, RouterModule } from '@angular/router';\nimport { TopologyGraphComponent } from './components/topologyGraph.component';\nimport { TopologyGraphService } from './services/topologyGraph.service';\n\n@NgModule({\n    imports: [RouterModule, CommonModule, ClarityModule, FormsModule],\n    declarations: [TopologyGraphComponent],\n    exports: [TopologyGraphComponent],\n    providers: [TopologyGraphService],\n    schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class TopologyGraphModule {\n\n}"
  },
  {
    "path": "ui/src/app/modules/topologyGraph/services/topologyGraph.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_URL } from '../../../app.constants';\n\n@Injectable()\nexport class TopologyGraphService {\n  constructor(private http: HttpClient) {\n\n  }\n\n  public getNodes(serviceName) {\n    let _devUrl: string = './json/nodes.json';\n    let _url: string = BACKEND_URL + 'nodes';\n    if (serviceName && serviceName !== 'ALL') {\n      _url = _url + '?service=' + serviceName;\n    }\n\n    return this.http.get(_url, {\n      observe: 'body',\n      responseType: 'json',\n      withCredentials: true\n    });\n  }\n\n  public getEdges(serviceName) {\n    let _devUrl: string = './json/edges.json';\n    let _url: string = BACKEND_URL + 'edges';\n    if (serviceName && serviceName !== 'ALL') {\n      _url = _url + '?service=' + serviceName;\n    }\n\n    return this.http.get(_url, {\n      observe: 'body',\n      responseType: 'json',\n      withCredentials: true\n    });\n  }\n\n  public getServiceList() {\n    let _devUrl: string = './json/serviceList.json';\n    let _url: string = BACKEND_URL + 'services';\n\n    return this.http.get(_url, {\n      observe: 'body',\n      responseType: 'json',\n      withCredentials: true\n    });\n  }\n}"
  },
  {
    "path": "ui/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "ui/src/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n#\n# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed\n\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\nnot IE 9-11"
  },
  {
    "path": "ui/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "ui/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * For easier debugging in development mode, you can import the following file\n * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.\n *\n * This import should be commented out in production mode because it will have a negative impact\n * on performance if an error is thrown.\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "ui/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <title>Purser</title>\n  <base href=\"/\">\n\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\">\n</head>\n\n<body>\n  <app-root>\n    <div class=\"loading-container\">\n      <span class=\"spinner\"> Loading... </span>\n    </div>\n  </app-root>\n</body>\n\n</html>"
  },
  {
    "path": "ui/src/json/logicalGroup.json",
    "content": "[\n    {\n      \"name\": \"vrbc\",\n      \"podsCount\": 882,\n      \"mtdCPU\": 662.475759,\n      \"cpu\": 56.9,\n      \"mtdCPUCost\": 15.899418,\n      \"mtdCost\": 306.798539,\n      \"mtdMemory\": 29043.16396,\n      \"mtdStorage\": 3365.862511,\n      \"memory\": 1951.792969,\n      \"storage\": 275,\n      \"mtdMemoryCost\": 290.43164,\n      \"mtdStorageCost\": 0.467481\n    },\n    {\n      \"name\": \"tango\",\n      \"podsCount\": 6,\n      \"mtdCPU\": 223.35569,\n      \"mtdMemory\": 589.78287,\n      \"cpu\": 14.55,\n      \"memory\": 38.406295,\n      \"mtdCPUCost\": 5.360537,\n      \"mtdMemoryCost\": 5.897829,\n      \"mtdCost\": 11.258366\n    },\n    {\n      \"name\": \"symphony\",\n      \"podsCount\": 3,\n      \"mtdCPU\": 206.529147,\n      \"mtdMemory\": 812.197643,\n      \"mtdStorage\": 49.76606,\n      \"cpu\": 12.45,\n      \"memory\": 48.960938,\n      \"storage\": 3,\n      \"mtdCPUCost\": 4.9567,\n      \"mtdMemoryCost\": 8.121976,\n      \"mtdStorageCost\": 0.006912,\n      \"mtdCost\": 13.085588\n    },\n    {\n      \"name\": \"ops-all\",\n      \"podsCount\": 19,\n      \"mtdCPU\": 1336.188258,\n      \"mtdMemory\": 6132.373898,\n      \"mtdStorage\": 289910.168038,\n      \"cpu\": 88.75,\n      \"memory\": 399.859913,\n      \"storage\": 24380,\n      \"mtdCPUCost\": 32.068518,\n      \"mtdMemoryCost\": 61.323739,\n      \"mtdStorageCost\": 40.265299,\n      \"mtdCost\": 133.657556\n    }\n  ]"
  },
  {
    "path": "ui/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};"
  },
  {
    "path": "ui/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic().bootstrapModule(AppModule)\n  .catch(err => console.log(err));\n\n"
  },
  {
    "path": "ui/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE9, IE10 and IE11 requires all of the following polyfills. **/\n// import 'core-js/es6/symbol';\n// import 'core-js/es6/object';\n// import 'core-js/es6/function';\n// import 'core-js/es6/parse-int';\n// import 'core-js/es6/parse-float';\n// import 'core-js/es6/number';\n// import 'core-js/es6/math';\n// import 'core-js/es6/string';\n// import 'core-js/es6/date';\n// import 'core-js/es6/array';\n// import 'core-js/es6/regexp';\n// import 'core-js/es6/map';\n// import 'core-js/es6/weak-map';\n// import 'core-js/es6/set';\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/** IE10 and IE11 requires the following for the Reflect API. */\n// import 'core-js/es6/reflect';\n\n\n/** Evergreen browsers require these. **/\n// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.\nimport 'core-js/es7/reflect';\n\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n **/\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n */\n\n // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n\n /*\n * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n * with the following flag, it will bypass `zone.js` patch for IE/Edge\n */\n// (window as any).__Zone_enable_cross_context_check = true;\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "ui/src/styles.css",
    "content": ".loading-container {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 0;\n  left: 0;\n}\n\n.loading-container .spinner {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 0;\n  left: 0;\n  margin: auto;\n}"
  },
  {
    "path": "ui/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "ui/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "ui/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "ui/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "ui/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es5\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "ui/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-redundant-jsdoc\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"use-input-property-decorator\": true,\n    \"use-output-property-decorator\": true,\n    \"use-host-property-decorator\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-life-cycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  }
]