├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── agent.go ├── agent_test.go ├── examples └── agent_daemonset.yml ├── glide.lock ├── glide.yaml ├── helm-chart └── netchecker-agent │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── _helpers.tpl │ └── daemonset.yaml │ └── values.yaml ├── lib └── uptimer │ ├── uptimer.go │ └── uptimer_test.go └── scripts ├── build_image_server_or_agent.sh ├── docker_publish.sh ├── helm_install_and_deploy.sh ├── import_images.sh └── kubeadm_dind_cluster.sh /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | gofmt: 3 | enabled: true 4 | golint: 5 | enabled: true 6 | #checks: 7 | # GoLint/Comments/DocComments: 8 | # enabled: false 9 | # GoLint/Imports/ImportDot: 10 | # enabled: false 11 | govet: 12 | enabled: true 13 | shellcheck: 14 | enabled: true 15 | markdownlint: 16 | enabled: true 17 | fixme: 18 | enabled: true 19 | config: 20 | strings: 21 | - FIXME 22 | - BUG 23 | - TODO 24 | ratings: 25 | paths: 26 | - "**.go" 27 | - "**.sh" 28 | - "**.md" 29 | #exclude_paths: 30 | # - "dir/dir/file.txt" 31 | # - "dir/*" 32 | # - spec/**/* 33 | # - "**/vendor/**/*" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _output 2 | vendor 3 | 4 | # CI related 5 | .build-image.complete 6 | .env-prepare.complete 7 | scripts/dind-cluster-v* 8 | scripts/get_helm.sh 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | language: go 5 | go: 6 | - 1.8.x 7 | install: 8 | - make get-deps 9 | script: 10 | - make test 11 | - make docker-publish -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM busybox 16 | 17 | MAINTAINER Artem Roma 18 | 19 | COPY _output/agent /usr/bin/netchecker-agent 20 | 21 | ENTRYPOINT ["netchecker-agent", "-logtostderr"] 22 | CMD ["-v=5"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | IMAGE_REPO_SERVER ?= mirantis/k8s-netchecker-server 17 | IMAGE_REPO_AGENT ?= mirantis/k8s-netchecker-agent 18 | HELM_SERVER_PATH ?= helm-chart/netchecker-server 19 | HELM_AGENT_PATH ?= helm-chart/netchecker-agent 20 | HELM_SCRIPT_NAME ?= get_helm.sh 21 | # repo for biuld agent docker image 22 | NETCHECKER_REPO ?= k8s-netchecker-server 23 | DOCKER_BUILD ?= no 24 | 25 | BUILD_DIR = _output 26 | VENDOR_DIR = vendor 27 | ROOT_DIR = $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 28 | 29 | # kubeadm-dind-cluster supports k8s versions: 30 | # "v1.6", "v1.7" and "v1.8". 31 | DIND_CLUSTER_VERSION ?= v1.8 32 | 33 | ENV_PREPARE_MARKER = .env-prepare.complete 34 | BUILD_IMAGE_MARKER = .build-image.complete 35 | 36 | 37 | ifeq ($(DOCKER_BUILD), yes) 38 | _DOCKER_GOPATH = /go 39 | _DOCKER_WORKDIR = $(_DOCKER_GOPATH)/src/github.com/Mirantis/k8s-netchecker-agent/ 40 | _DOCKER_IMAGE = golang:1.8 41 | DOCKER_DEPS = apt-get update; apt-get install -y libpcap-dev; 42 | DOCKER_EXEC = docker run --rm -it -v "$(ROOT_DIR):$(_DOCKER_WORKDIR)" \ 43 | -w "$(_DOCKER_WORKDIR)" $(_DOCKER_IMAGE) 44 | else 45 | DOCKER_EXEC = 46 | DOCKER_DEPS = 47 | endif 48 | 49 | 50 | .PHONY: help 51 | help: 52 | @echo "For containerized "make get-deps"" 53 | @echo "and "make test" export DOCKER_BUILD=yes" 54 | @echo "" 55 | @echo "Usage: 'make '" 56 | @echo "" 57 | @echo "Targets:" 58 | @echo "help - Print this message and exit" 59 | @echo "get-deps - Install project dependencies" 60 | @echo "build - Build k8s-netchecker-agent binary" 61 | @echo "containerized-build - Build k8s-netchecker-agent binary in container" 62 | @echo "build-image - Build docker image" 63 | @echo "test - Run all tests" 64 | @echo "unit - Run unit tests" 65 | @echo "e2e - Run e2e tests" 66 | @echo "docker-publish - Push images to Docker Hub registry" 67 | @echo "clean - Delete binaries" 68 | @echo "clean-k8s - Delete kubeadm-dind-cluster" 69 | @echo "clean-all - Delete binaries and vendor files" 70 | 71 | 72 | .PHONY: get-deps 73 | get-deps: $(VENDOR_DIR) 74 | 75 | 76 | .PHONY: build 77 | build: $(BUILD_DIR)/agent 78 | 79 | 80 | .PHONY: containerized-build 81 | containerized-build: 82 | make build DOCKER_BUILD=yes 83 | 84 | 85 | .PHONY: build-image 86 | build-image: $(BUILD_IMAGE_MARKER) 87 | 88 | 89 | .PHONY: unit 90 | unit: 91 | $(DOCKER_EXEC) go test -v $(glide novendor) 92 | 93 | 94 | .PHONY: e2e 95 | e2e: $(BUILD_DIR)/e2e.test $(ENV_PREPARE_MARKER) 96 | echo "TODO: sudo $(BUILD_DIR)/e2e.test" 97 | 98 | 99 | .PHONY: test 100 | test: unit e2e 101 | 102 | 103 | .PHONY: docker-publish 104 | docker-publish: 105 | IMAGE_REPO=$(IMAGE_REPO_AGENT) bash ./scripts/docker_publish.sh 106 | 107 | 108 | .PHONY: clean 109 | clean: 110 | rm -rf $(BUILD_DIR) 111 | 112 | 113 | .PHONY: clean-k8s 114 | clean-k8s: 115 | rm -f ./scripts/$(HELM_SCRIPT_NAME) 116 | rm -rf $(HOME)/.helm 117 | bash ./scripts/dind-cluster-$(DIND_CLUSTER_VERSION).sh clean 118 | rm -f ./scripts/dind-cluster-$(DIND_CLUSTER_VERSION).sh 119 | rm -rf $(HOME)/.kubeadm-dind-cluster 120 | rm -rf $(HOME)/.kube 121 | rm -f $(ENV_PREPARE_MARKER) 122 | 123 | 124 | .PHONY: clean-all 125 | clean-all: clean clean-k8s 126 | rm -rf $(VENDOR_DIR) 127 | docker rmi -f $(IMAGE_REPO_SERVER) 128 | docker rmi -f $(IMAGE_REPO_AGENT) 129 | rm -f $(BUILD_IMAGE_MARKER) 130 | 131 | 132 | $(BUILD_DIR): 133 | mkdir -p $(BUILD_DIR) 134 | 135 | 136 | $(VENDOR_DIR): 137 | $(DOCKER_EXEC) bash -xc 'go get github.com/Masterminds/glide && \ 138 | glide install --strip-vendor; \ 139 | chown $(shell id -u):$(shell id -g) -R $(VENDOR_DIR)' 140 | 141 | 142 | $(BUILD_DIR)/agent: $(BUILD_DIR) $(VENDOR_DIR) 143 | $(DOCKER_EXEC) bash -xc '$(DOCKER_DEPS) \ 144 | CGO_ENABLED=0 go build --ldflags "-s -w" \ 145 | -x -o $@ agent.go; \ 146 | chown $(shell id -u):$(shell id -g) -R $(BUILD_DIR)' 147 | 148 | 149 | $(BUILD_DIR)/e2e.test: $(BUILD_DIR) $(VENDOR_DIR) 150 | $(DOCKER_EXEC) echo "TODO: go test -c -o $@ ./test/e2e/" 151 | 152 | 153 | $(BUILD_IMAGE_MARKER): $(BUILD_DIR)/agent 154 | docker build -t $(IMAGE_REPO_AGENT) . 155 | touch $(BUILD_IMAGE_MARKER) 156 | 157 | 158 | $(ENV_PREPARE_MARKER): build-image 159 | NETCHECKER_REPO=$(NETCHECKER_REPO) bash ./scripts/build_image_server_or_agent.sh 160 | bash ./scripts/kubeadm_dind_cluster.sh 161 | IMAGE_REPO_SERVER=$(IMAGE_REPO_SERVER) IMAGE_REPO_AGENT=$(IMAGE_REPO_AGENT) bash ./scripts/import_images.sh 162 | NETCHECKER_REPO=$(NETCHECKER_REPO) bash ./scripts/helm_install_and_deploy.sh 163 | touch $(ENV_PREPARE_MARKER) 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network checker agent 2 | 3 | [![Build Status](https://goo.gl/ZeP2WM)](https://goo.gl/gQxrfz) 4 | [![Stories in Progress](https://goo.gl/D8FyiN)](https://goo.gl/kJ8CYj) 5 | [![Go Report Card](https://goo.gl/eHnKRa)](https://goo.gl/Q6HZdP) 6 | [![Code Climate](https://goo.gl/51gpev)](https://goo.gl/n5nWM4) 7 | [![License Apache 2.0](https://goo.gl/joRzTI)](https://goo.gl/QKY5kg) 8 | [![Docker Pulls](https://goo.gl/bsXWBB)](https://goo.gl/U0l9UK) 9 | 10 | The agent is a simple application that collects network related information 11 | from a host and sends it to designated network checker server end point. 12 | 13 | ## Usage 14 | 15 | `agent -v=5 -alsologtostderr=true -serverendopoint=0.0.0.0:8888 16 | -reportinterval=5` 17 | 18 | ## Building binary, running tests and preparing docker image 19 | 20 | Build static binary inside of intermediate build container: 21 | `make build-containerized` 22 | 23 | Prepare docker image: 24 | `make build-image` 25 | 26 | Run tests inside intermediate container: 27 | `export DOCKER_BUILD=yes; make unit` 28 | -------------------------------------------------------------------------------- /agent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "flag" 22 | "fmt" 23 | "github.com/Mirantis/k8s-netchecker-agent/lib/uptimer" 24 | "io" 25 | "net" 26 | "net/http" 27 | "net/http/httptrace" 28 | "net/url" 29 | "os" 30 | "reflect" 31 | "strings" 32 | "time" 33 | 34 | "github.com/golang/glog" 35 | "github.com/tcnksm/go-httpstat" 36 | "io/ioutil" 37 | ) 38 | 39 | const ( 40 | // EnvVarPodName is a pod name variable in pod's environment 41 | EnvVarPodName = "MY_POD_NAME" 42 | // EnvVarNodeName is a node name variable in pod's environment 43 | EnvVarNodeName = "MY_NODE_NAME" 44 | // NetcheckerAgentsEndpoint is a server URI where keepalive message is sent to 45 | NetcheckerAgentsEndpoint = "/api/v1/agents" 46 | // NetcheckerProbeEndpoint is a server URI that just provides simple 200 answer 47 | NetcheckerProbeEndpoint = "/api/v1/ping" 48 | ) 49 | 50 | // Client is a REST API client interface that matches standard http.Client struct and 51 | // references only Do() method from there. 52 | type Client interface { 53 | Do(req *http.Request) (*http.Response, error) 54 | } 55 | 56 | // Payload structure for keepalive message sent from agent to server 57 | type Payload struct { 58 | ReportInterval int `json:"report_interval"` 59 | NodeName string `json:"nodename"` 60 | PodName string `json:"podname"` 61 | HostDate time.Time `json:"hostdate"` 62 | Uptime uint64 `json:"uptime"` 63 | LookupHost map[string][]string `json:"nslookup"` 64 | IPs map[string][]string `json:"ips"` 65 | NetworkProbes []ProbeResult `json:"network_probes"` 66 | ZeroExtender []int8 `json:"zero_extender"` 67 | } 68 | 69 | // ProbeResult structure contains network probing result for one URL 70 | type ProbeResult struct { 71 | URL string 72 | ConnectionResult int 73 | HTTPCode int 74 | Total int 75 | ContentTransfer int 76 | TCPConnection int 77 | DNSLookup int 78 | Connect int 79 | ServerProcessing int 80 | } 81 | 82 | type connectionResult struct { 83 | Established bool 84 | } 85 | 86 | func sendInfo(srvEndpoint, podName string, nodeName string, probeRes []ProbeResult, 87 | repIntl int, extenderLength int, cl Client) (*http.Response, error) { 88 | 89 | reqURL := (&url.URL{ 90 | Scheme: "http", 91 | Host: srvEndpoint, 92 | Path: strings.Join([]string{NetcheckerAgentsEndpoint, podName}, "/"), 93 | }).String() 94 | 95 | glog.V(10).Infof("Probes result before marshaling: %v", probeRes) 96 | payload := &Payload{ 97 | HostDate: time.Now(), 98 | Uptime: uptimer.NewUptimer().Get(), 99 | IPs: linkV4Info(), 100 | ReportInterval: repIntl, 101 | NodeName: nodeName, 102 | PodName: podName, 103 | LookupHost: nsLookUp(srvEndpoint), 104 | NetworkProbes: probeRes, 105 | ZeroExtender: make([]int8, extenderLength), 106 | } 107 | 108 | glog.V(10).Infof("Request payload before marshaling: %v", payload) 109 | marshaled, err := json.Marshal(payload) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | glog.V(5).Infof("Send payload via URL: %v", reqURL) 115 | req, err := http.NewRequest("POST", reqURL, bytes.NewReader(marshaled)) 116 | if err != nil { 117 | return nil, err 118 | } 119 | req.Header.Add("Content-Type", "application/json") 120 | 121 | resp, err := cl.Do(req) 122 | if resp != nil { 123 | resp.Body.Close() 124 | } 125 | return resp, err 126 | } 127 | 128 | func nsLookUp(endpoint string) map[string][]string { 129 | hostname, _, err := net.SplitHostPort(endpoint) 130 | if err != nil { 131 | glog.Errorf("Error while splitting endpont %v. Details: %v", endpoint, err) 132 | } 133 | addrs, err := net.LookupHost(hostname) 134 | if err != nil { 135 | glog.Errorf("DNS look up host error. Details: %v", err) 136 | } 137 | result := map[string][]string{ 138 | hostname: addrs, 139 | } 140 | 141 | return result 142 | } 143 | 144 | func linkV4Info() map[string][]string { 145 | ifaces, err := net.Interfaces() 146 | if err != nil { 147 | glog.Errorf("Fail to collect information on ifaces. Details: %v", err) 148 | return nil 149 | } 150 | 151 | result := map[string][]string{} 152 | for _, i := range ifaces { 153 | addrs, err := i.Addrs() 154 | if err != nil { 155 | glog.Errorf("Fail get addresses for iface %v. Details: %v", i.Name, err) 156 | continue 157 | } 158 | 159 | addrArr := []string{} 160 | for _, a := range addrs { 161 | addrArr = append(addrArr, a.String()) 162 | } 163 | result[i.Name] = addrArr 164 | } 165 | glog.V(10).Infof("Addresses of host's links: %v", result) 166 | 167 | return result 168 | } 169 | 170 | func withConnectTrace(ctx context.Context, r *connectionResult) context.Context { 171 | return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ 172 | GotConn: func(i httptrace.GotConnInfo) { 173 | r.Established = true 174 | }, 175 | }) 176 | } 177 | 178 | func httpProbe(url string, probeRes *ProbeResult, client Client) { 179 | curRes := new(ProbeResult) 180 | curRes.URL = url 181 | curRes.ConnectionResult = 0 182 | *probeRes = *curRes 183 | 184 | req, err := http.NewRequest("GET", url, nil) 185 | if err != nil { 186 | glog.Error(err) 187 | return 188 | } 189 | 190 | // Create go-httpstat powered context and pass it to http.Request 191 | var result httpstat.Result 192 | var connection connectionResult 193 | ctxStat := httpstat.WithHTTPStat(req.Context(), &result) 194 | ctxStat = withConnectTrace(ctxStat, &connection) 195 | req = req.WithContext(ctxStat) 196 | 197 | res, err := client.Do(req) 198 | if err != nil { 199 | glog.Error(err) 200 | } 201 | if res != nil { 202 | curRes.HTTPCode = res.StatusCode 203 | } 204 | 205 | if err == nil { 206 | if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { 207 | glog.Error(err) 208 | } 209 | res.Body.Close() 210 | } 211 | t := time.Now() 212 | result.End(t) 213 | 214 | curRes.Total = int(result.Total(t) / time.Millisecond) 215 | if connection.Established { 216 | curRes.ConnectionResult = 1 217 | curRes.ContentTransfer = int(result.ContentTransfer(t) / time.Millisecond) 218 | curRes.Connect = int(result.Connect / time.Millisecond) 219 | curRes.DNSLookup = int(result.DNSLookup / time.Millisecond) 220 | curRes.ServerProcessing = int(result.ServerProcessing / time.Millisecond) 221 | curRes.TCPConnection = int(result.TCPConnection / time.Millisecond) 222 | } 223 | *probeRes = *curRes 224 | 225 | // keep variables order 226 | fields := []string{"Total", "ContentTransfer", "Connect", "DNSLookup", "ServerProcessing", 227 | "TCPConnection"} 228 | resStr := "" 229 | for _, field := range fields { 230 | resStr += (fmt.Sprintf("%s: %d ms; ", field, getFieldInteger(curRes, field))) 231 | } 232 | glog.V(5).Infof("HTTP Probe (%v): HTTPCode: %v; %v", url, curRes.HTTPCode, resStr) 233 | } 234 | 235 | func getFieldInteger(res *ProbeResult, field string) int { 236 | r := reflect.ValueOf(res) 237 | f := reflect.Indirect(r).FieldByName(field) 238 | return int(f.Int()) 239 | } 240 | 241 | func main() { 242 | var ( 243 | serverEndpoint string 244 | probeUrlsArg string 245 | reportInterval int 246 | extenderLength int 247 | ) 248 | 249 | flag.StringVar(&serverEndpoint, "serverendpoint", "netchecker-service:8081", 250 | "Netchecker server endpoint (host:port)") 251 | flag.StringVar(&probeUrlsArg, "probeurls", "", "HTTP servers URLs to measure "+ 252 | "access latency to (host:port/path)") 253 | flag.IntVar(&reportInterval, "reportinterval", 60, "Agent report interval (seconds)") 254 | flag.IntVar(&extenderLength, "zeroextenderlength", 1500, 255 | fmt.Sprint( 256 | "Length of zero bytes extender array ", 257 | "that will be added to the agent's payload ", 258 | "in case its size is less than MTU value. ", 259 | "Is used to reveal problems with network packets ", 260 | "fragmentation.", 261 | ), 262 | ) 263 | flag.Parse() 264 | 265 | glog.V(5).Infof("Provided server endpoint: %v", serverEndpoint) 266 | glog.V(5).Infof("Provided report interval: %v", reportInterval) 267 | 268 | glog.Info("Starting agent") 269 | 270 | var podName string 271 | var nodeName string 272 | if podName = os.Getenv(EnvVarPodName); len(podName) == 0 { 273 | glog.Error("Environment variable %s is not set. No point in sending info. Exiting", EnvVarPodName) 274 | os.Exit(1) 275 | } 276 | if nodeName = os.Getenv(EnvVarNodeName); len(nodeName) == 0 { 277 | glog.Error("Environment variable %s is not set.", EnvVarNodeName) 278 | } 279 | 280 | probeUrls := strings.FieldsFunc(probeUrlsArg, func(r rune) bool { 281 | return r == ',' || r == ';' 282 | }) 283 | serverURL := (&url.URL{ 284 | Scheme: "http", 285 | Host: serverEndpoint, 286 | Path: NetcheckerProbeEndpoint, 287 | }).String() 288 | probeUrls = append(probeUrls, serverURL) 289 | probeRes := make([]ProbeResult, len(probeUrls)) 290 | 291 | netTransport := &http.Transport{DisableKeepAlives: true} 292 | httpClient := &http.Client{ 293 | Timeout: time.Duration(reportInterval-1) * time.Second, 294 | Transport: netTransport, 295 | } 296 | 297 | for { 298 | for idx, probeURL := range probeUrls { 299 | go httpProbe(probeURL, &(probeRes[idx]), httpClient) 300 | } 301 | glog.V(4).Infof("Sleep for %v second(s)", reportInterval) 302 | time.Sleep(time.Duration(reportInterval) * time.Second) 303 | 304 | resp, err := sendInfo(serverEndpoint, podName, nodeName, probeRes, reportInterval, extenderLength, httpClient) 305 | if err != nil { 306 | glog.Errorf("Error while sending info. Details: %v", err) 307 | } else { 308 | glog.Infof("Response status code: %v", resp.StatusCode) 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /agent_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "net" 20 | "net/http" 21 | "reflect" 22 | "strings" 23 | "testing" 24 | ) 25 | 26 | type FakeHTTPClient struct { 27 | recordedRequest *http.Request 28 | } 29 | 30 | func (c *FakeHTTPClient) Do(req *http.Request) (*http.Response, error) { 31 | c.recordedRequest = req 32 | return nil, nil 33 | } 34 | 35 | func TestSendInfo(t *testing.T) { 36 | fakeClient := &FakeHTTPClient{} 37 | 38 | serverEndPoint := "localhost:8888" 39 | reportInterval := 5 40 | nodeName := strings.Split(serverEndPoint, ":")[0] 41 | podName := "test-pod" 42 | extenderLength := 100 43 | netProbes := []ProbeResult{ 44 | {"0.0.0.0:8081", 1, 200, 50, 1, 0, 0, 0, 0}, 45 | } 46 | _, err := sendInfo(serverEndPoint, podName, nodeName, netProbes, reportInterval, extenderLength, fakeClient) 47 | if err != nil { 48 | t.Errorf("sendInfo should not return error. Details: %v", err) 49 | } 50 | 51 | expectedURL := "http://" + serverEndPoint + NetcheckerAgentsEndpoint + "/" + podName 52 | if reqURL := fakeClient.recordedRequest.URL.String(); reqURL != expectedURL { 53 | t.Errorf("URL used in the request is not as expected. Actual %v", reqURL) 54 | } 55 | 56 | if fakeClient.recordedRequest.Method != "POST" { 57 | t.Error("Request does not use proper method (should be POST)") 58 | } 59 | 60 | ctJSON := false 61 | for _, ct := range fakeClient.recordedRequest.Header["Content-Type"] { 62 | if ct == "application/json" { 63 | ctJSON = true 64 | break 65 | } 66 | } 67 | 68 | if ctJSON == false { 69 | t.Error("Content-Type header must be properly set for header (correct - application/json)") 70 | } 71 | 72 | body := make([]byte, fakeClient.recordedRequest.ContentLength) 73 | _, err = fakeClient.recordedRequest.Body.Read(body) 74 | if err != nil { 75 | t.Errorf("Error should not occur while reading fake requests's body. Details: %v", err) 76 | } 77 | 78 | payload := &Payload{} 79 | err = json.Unmarshal(body, payload) 80 | if err != nil { 81 | t.Errorf("Error should not occur while unmarshaling fake request's payload. Details: %v", err) 82 | } 83 | 84 | if expectedIPs := linkV4Info(); !reflect.DeepEqual(expectedIPs, payload.IPs) { 85 | t.Errorf("IPs data from payload is not as expected. expected %v\n actual %v", expectedIPs, payload.IPs) 86 | } 87 | 88 | expectedHost := nodeName 89 | addrs, err := net.LookupHost(expectedHost) 90 | if err != nil { 91 | t.Errorf("DNS look up error should not occur. Details: %v", err) 92 | } 93 | if !reflect.DeepEqual(payload.LookupHost, map[string][]string{expectedHost: addrs}) { 94 | t.Errorf("LookupHost data from the payload is not as expected") 95 | } 96 | 97 | if len(payload.ZeroExtender) != extenderLength { 98 | t.Errorf("Extender should be of %v len instead it is %v", extenderLength, 99 | len(payload.ZeroExtender)) 100 | } 101 | if payload.NodeName != nodeName { 102 | t.Errorf("Node name from payload (%v) does not match expected one (%v)", payload.NodeName, nodeName) 103 | } 104 | if !reflect.DeepEqual(payload.NetworkProbes, netProbes) { 105 | t.Errorf("NetworkProbes data from the payload is not as expected") 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/agent_daemonset.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: DaemonSet 3 | metadata: 4 | labels: 5 | app: netchecker-agent 6 | name: netchecker-agent 7 | spec: 8 | template: 9 | metadata: 10 | name: netchecker-agent 11 | labels: 12 | app: netchecker-agent 13 | spec: 14 | containers: 15 | - name: netchecker-agent 16 | image: mirantis/k8s-netchecker-agent:latest 17 | env: 18 | - name: MY_NODE_NAME 19 | valueFrom: 20 | fieldRef: 21 | fieldPath: spec.nodeName 22 | - name: MY_POD_NAME 23 | valueFrom: 24 | fieldRef: 25 | fieldPath: metadata.name 26 | args: 27 | - "-v=5" 28 | - "-alsologtostderr=true" 29 | - "-serverendpoint=netchecker-service:8081" 30 | - "-reportinterval=60" 31 | - "-probeurls=http://ipinfo.io" 32 | imagePullPolicy: Always 33 | nodeSelector: 34 | netchecker: agent 35 | --- 36 | apiVersion: extensions/v1beta1 37 | kind: DaemonSet 38 | metadata: 39 | labels: 40 | app: netchecker-agent-hostnet 41 | name: netchecker-agent-hostnet 42 | spec: 43 | template: 44 | metadata: 45 | name: netchecker-agent-hostnet 46 | labels: 47 | app: netchecker-agent-hostnet 48 | spec: 49 | hostNetwork: True 50 | containers: 51 | - name: netchecker-agent 52 | image: mirantis/k8s-netchecker-agent:latest 53 | env: 54 | - name: MY_NODE_NAME 55 | valueFrom: 56 | fieldRef: 57 | fieldPath: spec.nodeName 58 | - name: MY_POD_NAME 59 | valueFrom: 60 | fieldRef: 61 | fieldPath: metadata.name 62 | args: 63 | - "-v=5" 64 | - "-alsologtostderr=true" 65 | - "-serverendpoint=netchecker-service:8081" 66 | - "-reportinterval=60" 67 | imagePullPolicy: Always 68 | nodeSelector: 69 | netchecker: agent 70 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: c37496539577ed2757533ba06e9fd94ec11bd71be3e3c556d05c93013751ed1c 2 | updated: 2017-05-08T13:50:59.697335135+03:00 3 | imports: 4 | - name: github.com/golang/glog 5 | version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998 6 | - name: github.com/tcnksm/go-httpstat 7 | version: ae0d799e7ee65d3dc46d7272368daa830aef6c5a 8 | testImports: [] 9 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/Mirantis/k8s-netchecker-agent 2 | import: 3 | - package: github.com/golang/glog 4 | - package: github.com/tcnksm/go-httpstat 5 | version: v0.2.0 6 | -------------------------------------------------------------------------------- /helm-chart/netchecker-agent/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm-chart/netchecker-agent/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: A simple network checker for kubernetes (agent-part) 3 | name: netchecker-agent 4 | version: v1.0 5 | maintainers: 6 | - name: Artem Roma 7 | email: aroma@mirantis.com 8 | -------------------------------------------------------------------------------- /helm-chart/netchecker-agent/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 24 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}} 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /helm-chart/netchecker-agent/templates/daemonset.yaml: -------------------------------------------------------------------------------- 1 | {{- $container := .Values.container -}} 2 | {{- $image := .Values.image -}} 3 | {{- range $agent_name := .Values.agents.names }} 4 | apiVersion: extensions/v1beta1 5 | kind: DaemonSet 6 | metadata: 7 | labels: 8 | app: {{ $agent_name }} 9 | name: {{ $agent_name }} 10 | spec: 11 | template: 12 | metadata: 13 | name: {{ $agent_name }} 14 | labels: 15 | app: {{ $agent_name }} 16 | spec: 17 | {{ if eq $agent_name "netchecker-agent-hostnet" }}hostNetwork: True{{ end }} 18 | containers: 19 | - name: {{ $container.name }} 20 | image: {{ $image.repository }}:{{ $image.tag }} 21 | imagePullPolicy: {{ $image.pullPolicy }} 22 | env: 23 | - name: MY_POD_NAME 24 | valueFrom: 25 | fieldRef: 26 | fieldPath: metadata.name 27 | args: 28 | {{- range $container.args }} 29 | - {{ . | quote }} 30 | {{- end}} 31 | --- 32 | {{- end }} 33 | -------------------------------------------------------------------------------- /helm-chart/netchecker-agent/values.yaml: -------------------------------------------------------------------------------- 1 | image: 2 | repository: mirantis/k8s-netchecker-agent 3 | tag: latest 4 | pullPolicy: Always 5 | 6 | container: 7 | name: netchecker-agent 8 | args: 9 | - -v=5 10 | - -logtostderr 11 | - -serverendpoint=netchecker-service:8081 12 | - -reportinterval=60 13 | 14 | # Daemonset description for agent and agent-hostnet 15 | # are different only by name of the application 16 | # (subsequently by 'app' label value), so templates 17 | # for corresponding resources are instantinated 18 | # in range loop that iterates over following names 19 | agents: 20 | names: 21 | - netchecker-agent 22 | - netchecker-agent-hostnet 23 | -------------------------------------------------------------------------------- /lib/uptimer/uptimer.go: -------------------------------------------------------------------------------- 1 | package uptimer 2 | 3 | import ( 4 | "github.com/golang/glog" 5 | "io/ioutil" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type AppUptimer struct{} 12 | 13 | const ( 14 | PROC_UPTIME = "/proc/uptime" 15 | ) 16 | 17 | var main_uptimer *AppUptimer 18 | 19 | var readFile = ioutil.ReadFile // for Mock() in the test 20 | 21 | func (u *AppUptimer) getUptimeString() string { 22 | buf, err := readFile(PROC_UPTIME) 23 | if err != nil { 24 | glog.Error("Can't read '%s'", PROC_UPTIME) 25 | return "0" 26 | } 27 | rv := strings.Split(string(buf), " ")[0] 28 | return rv 29 | } 30 | 31 | func (u *AppUptimer) GetFloat() float64 { 32 | sbuf := u.getUptimeString() 33 | rv, err := strconv.ParseFloat(sbuf, 64) 34 | if err != nil { 35 | glog.Error("Can't convert '%s' to float64", sbuf) 36 | return 0 37 | } 38 | return float64(rv) 39 | } 40 | 41 | func (u *AppUptimer) Get() uint64 { 42 | sbuf := u.getUptimeString() 43 | sbuf = strings.Split(sbuf, ".")[0] 44 | rv, err := strconv.ParseInt(sbuf, 10, 64) 45 | if err != nil { 46 | glog.Error("Can't convert '%s' to uint64", sbuf) 47 | return 0 48 | } 49 | return uint64(rv) 50 | } 51 | 52 | type Uptimer interface { 53 | getUptimeString() string 54 | GetFloat() float64 55 | Get() uint64 56 | } 57 | 58 | func NewUptimer() *AppUptimer { 59 | // Uptimer is a singletone 60 | return main_uptimer 61 | } 62 | 63 | func init() { 64 | if _, err := os.Stat(PROC_UPTIME); os.IsNotExist(err) || os.IsPermission(err) { 65 | glog.Fatalf("File '%s' does not exists or You have no permissons to read it.", PROC_UPTIME) 66 | } 67 | main_uptimer = new(AppUptimer) 68 | } 69 | -------------------------------------------------------------------------------- /lib/uptimer/uptimer_test.go: -------------------------------------------------------------------------------- 1 | package uptimer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_getUptimeString(t *testing.T) { 8 | oldReadFile := readFile 9 | defer func() { readFile = oldReadFile }() 10 | readFile = func(filename string) ([]byte, error) { 11 | return []byte("888.999 333.222"), nil 12 | } 13 | 14 | u := NewUptimer() 15 | if r := u.getUptimeString(); "888.999" != r { 16 | t.Errorf("returned '%s', should be 888.999", r) 17 | } 18 | } 19 | 20 | func Test_Get_(t *testing.T) { 21 | oldReadFile := readFile 22 | defer func() { readFile = oldReadFile }() 23 | readFile = func(filename string) ([]byte, error) { 24 | return []byte("888.999 333.222"), nil 25 | } 26 | 27 | u := NewUptimer() 28 | if r := u.Get(); 888 != r { 29 | t.Errorf("returned %d, should be 888", r) 30 | } 31 | if r := u.GetFloat(); 888.999 != r { 32 | t.Errorf("returned %d, should be 888.999", r) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scripts/build_image_server_or_agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Mirantis 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o xtrace 17 | set -o pipefail 18 | set -o errexit 19 | set -o nounset 20 | 21 | 22 | NETCHECKER_REPO=${NETCHECKER_REPO:-} 23 | NETCHECKER_BRANCH=${NETCHECKER_BRANCH:-master} 24 | BUILD_IMAGE_MARKER=${BUILD_IMAGE_MARKER:-.build-image.complete} 25 | 26 | 27 | function build-image-server-or-agent { 28 | if [ -z "${NETCHECKER_REPO}" ]; then 29 | echo "NETCHECKER_REPO is not set!" 30 | exit 1 31 | else 32 | pushd "../" &> /dev/null 33 | if [ ! -d "${NETCHECKER_REPO}" ]; then 34 | git clone --branch "${NETCHECKER_BRANCH}" \ 35 | --depth 1 --single-branch "https://github.com/Mirantis/${NETCHECKER_REPO}.git" 36 | fi 37 | fi 38 | pushd "./${NETCHECKER_REPO}" &> /dev/null 39 | make build-image 40 | rm -f "${BUILD_IMAGE_MARKER}" 41 | popd &> /dev/null 42 | popd &> /dev/null 43 | } 44 | 45 | build-image-server-or-agent 46 | -------------------------------------------------------------------------------- /scripts/docker_publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Mirantis 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o xtrace 17 | set -o pipefail 18 | set -o errexit 19 | set -o nounset 20 | 21 | 22 | TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-} 23 | TRAVIS_TEST_RESULT=${TRAVIS_TEST_RESULT:-} 24 | TRAVIS_BRANCH=${TRAVIS_BRANCH:-} 25 | IMAGE_REPO=${IMAGE_REPO:-} 26 | TRAVIS_TAG=${TRAVIS_TAG:-} 27 | 28 | 29 | function push-to-docker { 30 | if [ -z "${TRAVIS_TEST_RESULT}" ]; then 31 | echo "TRAVIS_TEST_RESULT is not set!" 32 | exit 1 33 | else 34 | if [ "${TRAVIS_TEST_RESULT}" -ne 0 ]; then 35 | echo "Some of the previous steps ended with an errors! The build is broken!" 36 | exit 1 37 | fi 38 | fi 39 | 40 | if [ -n "${TRAVIS_PULL_REQUEST_BRANCH}" ]; then 41 | echo "Processing PR ${TRAVIS_PULL_REQUEST_BRANCH}" 42 | exit 0 43 | else 44 | set +o xtrace 45 | docker login -u="${DOCKER_USERNAME}" -p="${DOCKER_PASSWORD}" 46 | set -o xtrace 47 | fi 48 | 49 | if [ -z "${IMAGE_REPO}" ]; then 50 | echo "IMAGE_REPO is not set!" 51 | exit 1 52 | fi 53 | 54 | if [ -n "${TRAVIS_TAG}" ]; then 55 | echo "Pushing with tag - ${TRAVIS_TAG}" 56 | docker tag "${IMAGE_REPO}" "${IMAGE_REPO}":"${TRAVIS_TAG}" 57 | docker push "${IMAGE_REPO}":"${TRAVIS_TAG}" 58 | exit 59 | fi 60 | 61 | if [ "${TRAVIS_BRANCH}" == "master" ]; then 62 | echo "Pushing with tag - latest" 63 | docker push "${IMAGE_REPO}":latest 64 | exit 65 | fi 66 | 67 | echo "Pushing with tag - ${TRAVIS_BRANCH}" 68 | docker tag "${IMAGE_REPO}" "${IMAGE_REPO}":"${TRAVIS_BRANCH}" 69 | docker push "${IMAGE_REPO}":"${TRAVIS_BRANCH}" 70 | } 71 | 72 | push-to-docker 73 | -------------------------------------------------------------------------------- /scripts/helm_install_and_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Mirantis 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o xtrace 17 | set -o pipefail 18 | set -o errexit 19 | set -o nounset 20 | 21 | 22 | HELM_SCRIPT_URL=${HELM_SCRIPT_URL:-https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get} 23 | HELM_SCRIPT_NAME=${HELM_SCRIPT_NAME:-get_helm.sh} 24 | HELM_SERVER_PATH=${HELM_SERVER_PATH:-helm-chart/netchecker-server} 25 | HELM_AGENT_PATH=${HELM_AGENT_PATH:-helm-chart/netchecker-agent} 26 | HELM_DEBUG=${HELM_DEBUG:-"--debug"} 27 | NETCHECKER_REPO=${NETCHECKER_REPO:-} 28 | KUBECTL_DIR="${KUBECTL_DIR:-${HOME}/.kubeadm-dind-cluster}" 29 | PATH="${KUBECTL_DIR}:${PATH}" 30 | NS=${NS:-netchecker} 31 | REAL_NS="--namespace=${1:-$NS}" 32 | 33 | 34 | function wait-for-tiller-pod-ready() { 35 | local name="${1}" 36 | local timeout_secs=60 37 | local increment_secs=1 38 | local waited_time=0 39 | 40 | while [ "${waited_time}" -lt "${timeout_secs}" ]; do 41 | tiller_replicas="$(kubectl get deployment "${name}" \ 42 | -o 'go-template={{.status.availableReplicas}}' \ 43 | --namespace kube-system)" 44 | 45 | if [ "${tiller_replicas}" == "1" ]; then 46 | return 0 47 | fi 48 | 49 | sleep "${increment_secs}" 50 | (( waited_time += increment_secs )) 51 | 52 | if [ "${waited_time}" -ge "${timeout_secs}" ]; then 53 | echo "${name} was never ready." 54 | exit 1 55 | fi 56 | echo -n . 1>&2 57 | done 58 | } 59 | 60 | 61 | function install-helm { 62 | pushd "./scripts" &> /dev/null 63 | wget -O "${HELM_SCRIPT_NAME}" "${HELM_SCRIPT_URL}" 64 | chmod +x ./"${HELM_SCRIPT_NAME}" 65 | set +o errexit 66 | bash -x ./"${HELM_SCRIPT_NAME}" 67 | echo "Uninstall tiller-deploy if exists" 68 | kubectl delete deployment "tiller-deploy" --namespace "kube-system" &> /dev/null || true 69 | set -o errexit 70 | helm "${HELM_DEBUG}" init 71 | wait-for-tiller-pod-ready "tiller-deploy" 72 | helm "${HELM_DEBUG}" version 73 | popd &> /dev/null 74 | } 75 | 76 | 77 | function lint-helm { 78 | if [ -z "${NETCHECKER_REPO}" ]; then 79 | echo "NETCHECKER_REPO is not set!" 80 | exit 1 81 | fi 82 | if [ "${NETCHECKER_REPO}" == "k8s-netchecker-server" ]; then 83 | helm "${HELM_DEBUG}" lint ./"${HELM_AGENT_PATH}"/ 84 | else 85 | helm "${HELM_DEBUG}" lint ./"${HELM_SERVER_PATH}"/ 86 | fi 87 | } 88 | 89 | 90 | function deploy-helm { 91 | if [ "${NETCHECKER_REPO}" == "k8s-netchecker-server" ]; then 92 | pushd "../${NETCHECKER_REPO}" &> /dev/null 93 | helm "${HELM_DEBUG}" install ${REAL_NS} ./"${HELM_SERVER_PATH}"/ 94 | popd &> /dev/null 95 | helm "${HELM_DEBUG}" install ${REAL_NS} ./"${HELM_AGENT_PATH}"/ 96 | else 97 | helm "${HELM_DEBUG}" install ${REAL_NS} ./"${HELM_SERVER_PATH}"/ 98 | pushd "../${NETCHECKER_REPO}" &> /dev/null 99 | helm "${HELM_DEBUG}" install ${REAL_NS} ./"${HELM_AGENT_PATH}"/ 100 | popd &> /dev/null 101 | fi 102 | helm "${HELM_DEBUG}" list 103 | } 104 | 105 | 106 | install-helm 107 | lint-helm 108 | deploy-helm 109 | -------------------------------------------------------------------------------- /scripts/import_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Mirantis 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o xtrace 17 | set -o pipefail 18 | set -o errexit 19 | set -o nounset 20 | 21 | 22 | IMAGE_REPO_SERVER=${IMAGE_REPO_SERVER:-mirantis/k8s-netchecker-server} 23 | IMAGE_REPO_AGENT=${IMAGE_REPO_AGENT:-mirantis/k8s-netchecker-agent} 24 | IMAGE_TAG=${IMAGE_TAG:-latest} 25 | NUM_NODES=${NUM_NODES:-3} 26 | TMP_IMAGE_PATH=${TMP_IMAGE_PATH:-/tmp/netchecker-all.tar} 27 | # export MASTER_NAME=kube-master if you need 28 | # to import images in kube-master node 29 | MASTER_NAME=${MASTER_NAME:-} 30 | SLAVE_NAME=${SLAVE_NAME:-"kube-node-"} 31 | 32 | 33 | function import-images { 34 | docker save -o "${TMP_IMAGE_PATH}" \ 35 | "${IMAGE_REPO_SERVER}":"${IMAGE_TAG}" "${IMAGE_REPO_AGENT}":"${IMAGE_TAG}" 36 | 37 | if [ ! -z "${MASTER_NAME}" ]; then 38 | docker cp "${TMP_IMAGE_PATH}" "${MASTER_NAME}":/netchecker-all.tar 39 | docker exec -ti "${MASTER_NAME}" docker load -i /netchecker-all.tar 40 | docker exec -ti "${MASTER_NAME}" docker images 41 | fi 42 | 43 | for node in $(seq 1 "${NUM_NODES}"); do 44 | docker cp "${TMP_IMAGE_PATH}" "${SLAVE_NAME}""${node}":/netchecker-all.tar 45 | docker exec -ti "${SLAVE_NAME}""${node}" docker load -i /netchecker-all.tar 46 | docker exec -ti "${SLAVE_NAME}""${node}" docker images 47 | done 48 | echo "Finished copying docker images to dind nodes" 49 | } 50 | 51 | import-images 52 | -------------------------------------------------------------------------------- /scripts/kubeadm_dind_cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Mirantis 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o xtrace 17 | set -o pipefail 18 | set -o errexit 19 | set -o nounset 20 | 21 | 22 | NUM_NODES=${NUM_NODES:-3} 23 | KUBEADM_SCRIPT_URL=${KUBEADM_SCRIPT_URL:-https://cdn.rawgit.com/Mirantis/kubeadm-dind-cluster/master/fixed/dind-cluster} 24 | # kubeadm-dind-cluster supports k8s versions: 25 | # "v1.6", "v1.7" and "v1.8". 26 | DIND_CLUSTER_VERSION=${DIND_CLUSTER_VERSION:-v1.8} 27 | 28 | 29 | function kubeadm-dind-cluster { 30 | pushd "./scripts" &> /dev/null 31 | wget "${KUBEADM_SCRIPT_URL}-${DIND_CLUSTER_VERSION}.sh" 32 | chmod +x ./dind-cluster-"${DIND_CLUSTER_VERSION}".sh 33 | NUM_NODES="${NUM_NODES}" bash ./dind-cluster-"${DIND_CLUSTER_VERSION}".sh down 34 | NUM_NODES="${NUM_NODES}" bash ./dind-cluster-"${DIND_CLUSTER_VERSION}".sh up 35 | popd &> /dev/null 36 | } 37 | 38 | kubeadm-dind-cluster 39 | --------------------------------------------------------------------------------