diff --git a/.github/ISSUE_TEMPLATE/product-backlog-item.yml b/.github/ISSUE_TEMPLATE/product-backlog-item.yml index ddbd79dc..0fb661ce 100644 --- a/.github/ISSUE_TEMPLATE/product-backlog-item.yml +++ b/.github/ISSUE_TEMPLATE/product-backlog-item.yml @@ -33,8 +33,6 @@ body: - [ ] **Criterion 1**: Detailed description of the first acceptance criterion. description: | Define the conditions that must be met for the item to be considered complete. - validations: - required: true - type: checkboxes id: definition_of_done attributes: @@ -59,7 +57,7 @@ body: - type: textarea id: dependencies attributes: - label: Assumptions and Constraints + label: Dependencies placeholder: |- - **Dependency 1**: Detailed description of the first dependency. - type: textarea diff --git a/.github/workflow_scripts/remove-docker-images.sh b/.github/workflow_scripts/remove-docker-images.sh new file mode 100755 index 00000000..1b79b326 --- /dev/null +++ b/.github/workflow_scripts/remove-docker-images.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +MODE="UNKNOWN" +PATTERN="" +while [[ $# -gt 0 ]]; do + case $1 in + rm) + MODE="REMOVE" + shift + ;; + ls) + MODE="LIST" + shift + ;; + --pattern) + PATTERN=$2 + shift + shift + ;; + --filter) + PATTERN=$2 + shift + shift + ;; + *) + echo "Invalid argument: $1" >&2 + exit 1 + ;; + esac +done + +if [ "$MODE" == "UNKNOWN" ]; then + echo "You need to specify the mode (rm, ls) with the first argument" + exit 1 +fi + +if [ "$PATTERN" == "" ]; then + PATTERN=".*" +fi + +function list() { + OUT=$(hub-tool tag ls cjlapao/prl-devops-service --format json) + LINES=$(echo "$OUT" | jq -r '.[].Name') + echo "$LINES" | while IFS= read -r line; do + if [[ $line =~ $PATTERN ]]; then + echo "$line" + fi + done +} + +function remove() { + echo "WARNING: You are about to permanently delete images that match the pattern: $PATTERN" + echo " This action is irreversible" + read -r -p "Are you sure you want to continue? (yes/no): " confirm + if [ "$confirm" != "yes" ]; then + echo "Operation aborted." + exit 1 + fi + + lines=$(list) + echo "$lines" | while IFS= read -r line; do + echo "Deleting image $line" + hub-tool tag rm "$line" -f + done +} + +if [ "$MODE" == "LIST" ]; then + list +elif [ "$MODE" == "REMOVE" ]; then + remove "$PATTERN" +fi diff --git a/Dockerfile b/Dockerfile index 21b77bec..f14698d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 ############################ # STEP 1 build executable binary ############################ @@ -15,7 +16,18 @@ COPY ./src . RUN go get -d -v # Build the binary. -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /go/bin/prl-devops-service +ARG BUILD_ENV=production +ARG VERSION +ARG OS=linux +ARG ARCHITECTURE=amd64 + +RUN --mount=type=secret,id=amplitude_api_key,env=AMPLITUDE_API_KEY + +RUN if [ "$BUILD_ENV" = "production" ]; then \ + CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCHITECTURE go build -ldflags="-s -w -X main.ver=$VERSION -X 'github.com/Parallels/prl-devops-service/telemetry.AmplitudeApiKey=$AMPLITUDE_API_KEY'" -o /go/bin/prl-devops-service; \ + else \ + CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCHITECTURE go build -ldflags="-s -w -X main.ver=$VERSION" -o /go/bin/prl-devops-service; \ + fi ############################ # STEP 2 build a small image @@ -23,12 +35,6 @@ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /go/bin/prl-devops-ser FROM alpine:latest RUN apk update && apk add curl coreutils bash musl-utils ca-certificates -# Copy our static executable. -# COPY --from=builder /bin/cat /bin/cat -# COPY --from=builder /usr/bin/whoami /usr/bin/whoami -# COPY --from=builder /usr/bin/getent /usr/bin/getent -# COPY --from=builder /bin/uname /usr/bin/uname -# COPY --from=builder /usr/bin/id /usr/bin/id COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /tmp/ diff --git a/Makefile b/Makefile index 6feb60e3..676b4028 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,13 @@ NAME ?= prldevops export PACKAGE_NAME ?= $(NAME) +export DOCKER_PACKAGE_NAME ?= "prl-devops-service" ifeq ($(OS),Windows_NT) - export VERSION=$(shell type VERSION) + export VERSION:=$(shell type VERSION) else - export VERSION=$(shell cat VERSION) + export VERSION:=$(shell cat VERSION) + export BUILD_ID:=$(shell date +%s) + export SHORT_VERSION:=$(shell echo $(VERSION) | cut -d'.' -f1,2) + export BUILD_VERSION:=$(shell echo $(SHORT_VERSION).$(BUILD_ID)) endif COBERTURA = cobertura @@ -104,15 +108,45 @@ endif @cd src && CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o ../out/binaries/$(PACKAGE_NAME)-alpine @echo "Build finished." +.PHONY: push-alpha-container +push-alpha-container: + @echo "Building $(BUILD_VERSION) Alpha Container..." + @docker build -t cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_alpha \ + -t cjlapao/$(DOCKER_PACKAGE_NAME):latest_alpha \ + --build-arg VERSION=$(BUILD_VERSION) \ + --build-arg BUILD_ENV=debug \ + --build-arg OS=linux \ + --build-arg ARCHITECTURE=amd64 \ + -f Dockerfile . + @echo "Pushing $(BUILD_VERSION) Container..." + @echo "Pushing cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_alpha tag..." + @docker push cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_alpha + @echo "Pushing cjlapao/$(DOCKER_PACKAGE_NAME):latest_alpha tag..." + @docker push cjlapao/$(DOCKER_PACKAGE_NAME):latest_alpha + @echo "Build finished. Pushed to cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_alpha and cjlapao/$(DOCKER_PACKAGE_NAME):latest_alpha." + +.PHONY: clean-alpha +clean-alpha-container: + @echo "Removing all alpha versions from Docker Hub..." + @./.github/workflow_scripts/remove-docker-images.sh rm --filter '.*alpha.*$' + @echo "All alpha versions removed." + .PHONY: push-beta-container push-beta-container: - @echo "Building..." -ifeq ($(wildcard ./out/.*),) - @echo "Creating out directory..." - @mkdir out - @mkdir out/binaries -endif - @echo "Build finished." + @echo "Building $(BUILD_VERSION) Beta Container..." + @docker build -t cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_beta \ + -t cjlapao/$(DOCKER_PACKAGE_NAME):unstable \ + --build-arg VERSION=$(BUILD_VERSION) \ + --build-arg BUILD_ENV=debug \ + --build-arg OS=linux \ + --build-arg ARCHITECTURE=amd64 \ + -f Dockerfile . + @echo "Pushing $(BUILD_VERSION) Container..." + @echo "Pushing cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_beta tag..." + @docker push cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_beta + @echo "Pushing cjlapao/$(DOCKER_PACKAGE_NAME):unstable tag..." + @docker push cjlapao/$(DOCKER_PACKAGE_NAME):unstable + @echo "Build finished. Pushed to cjlapao/$(DOCKER_PACKAGE_NAME):$(BUILD_VERSION)_beta and cjlapao/$(DOCKER_PACKAGE_NAME):unstable." .PHONY: clean clean: diff --git a/src/controllers/common.go b/src/controllers/common.go index 316f3685..bbdbf164 100644 --- a/src/controllers/common.go +++ b/src/controllers/common.go @@ -4,8 +4,10 @@ import ( "encoding/json" "fmt" "net/http" + "runtime/debug" "github.com/Parallels/prl-devops-service/basecontext" + "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/models" ) @@ -21,10 +23,16 @@ func GetBaseContext(r *http.Request) *basecontext.BaseContext { func Recover(ctx basecontext.ApiContext, r *http.Request, w http.ResponseWriter) { if err := recover(); err != nil { - ctx.LogErrorf("Recovered from panic: %v", err) - ReturnApiError(ctx, w, models.NewFromErrorWithCode(fmt.Errorf("internal server error"), http.StatusInternalServerError)) + ctx.LogErrorf("Recovered from panic: %v\n%v", err, debug.Stack()) + sysErr := errors.NewWithCodef(http.StatusInternalServerError, "internal server error") + sysErr.NestedError = make([]errors.NestedError, 0) + sysErr.NestedError = append(sysErr.NestedError, errors.NestedError{ + Message: fmt.Sprintf("%v", err.(error)), + }, errors.NestedError{ + Message: string(debug.Stack()), + }) - fmt.Printf("Recovered from panic: %v", err) + ReturnApiError(ctx, w, models.NewFromErrorWithCode(sysErr, http.StatusInternalServerError)) } } diff --git a/src/controllers/orchestrator.go b/src/controllers/orchestrator.go index 751f9faa..d7d00e14 100644 --- a/src/controllers/orchestrator.go +++ b/src/controllers/orchestrator.go @@ -749,7 +749,12 @@ func GetOrchestratorOverviewHandler() restapi.ControllerHandler { defer Recover(ctx, r, w) orchestratorSvc := orchestrator.NewOrchestratorService(ctx) result := make([]models.HostResourceOverviewResponse, 0) - resources, err := orchestratorSvc.GetResources(ctx) + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } + + resources, err := orchestratorSvc.GetResources(ctx, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -825,8 +830,12 @@ func GetOrchestratorVirtualMachinesHandler() restapi.ControllerHandler { filter := GetFilterHeader(r) defer Recover(ctx, r, w) orchestratorSvc := orchestrator.NewOrchestratorService(ctx) + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } - vms, err := orchestratorSvc.GetVirtualMachines(ctx, filter) + vms, err := orchestratorSvc.GetVirtualMachines(ctx, filter, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -862,8 +871,12 @@ func GetOrchestratorVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } - vm, err := orchestratorSvc.GetVirtualMachine(ctx, id) + vm, err := orchestratorSvc.GetVirtualMachine(ctx, id, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -929,8 +942,12 @@ func GetOrchestratorVirtualMachineStatusHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } - response, err := orchestratorSvc.GetVirtualMachineStatus(ctx, id) + response, err := orchestratorSvc.GetVirtualMachineStatus(ctx, id, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1013,6 +1030,10 @@ func SetOrchestratorVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -1029,7 +1050,7 @@ func SetOrchestratorVirtualMachineHandler() restapi.ControllerHandler { return } - response, err := orchestratorSvc.ConfigureVirtualMachine(ctx, id, request) + response, err := orchestratorSvc.ConfigureVirtualMachine(ctx, id, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1061,6 +1082,10 @@ func StartOrchestratorVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } request := models.VirtualMachineConfigRequest{ Operations: []*models.VirtualMachineConfigRequestOperation{ @@ -1071,7 +1096,7 @@ func StartOrchestratorVirtualMachineHandler() restapi.ControllerHandler { }, } - response, err := orchestratorSvc.ConfigureVirtualMachine(ctx, id, request) + response, err := orchestratorSvc.ConfigureVirtualMachine(ctx, id, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1103,6 +1128,10 @@ func StopOrchestratorVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } request := models.VirtualMachineConfigRequest{ Operations: []*models.VirtualMachineConfigRequestOperation{ @@ -1113,7 +1142,7 @@ func StopOrchestratorVirtualMachineHandler() restapi.ControllerHandler { }, } - response, err := orchestratorSvc.ConfigureVirtualMachine(ctx, id, request) + response, err := orchestratorSvc.ConfigureVirtualMachine(ctx, id, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1146,6 +1175,10 @@ func ExecutesOrchestratorVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -1162,7 +1195,7 @@ func ExecutesOrchestratorVirtualMachineHandler() restapi.ControllerHandler { return } - response, err := orchestratorSvc.ExecuteOnVirtualMachine(ctx, id, request) + response, err := orchestratorSvc.ExecuteOnVirtualMachine(ctx, id, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1194,8 +1227,12 @@ func GetOrchestratorHostVirtualMachinesHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } - vms, err := orchestratorSvc.GetHostVirtualMachines(ctx, id, GetFilterHeader(r)) + vms, err := orchestratorSvc.GetHostVirtualMachines(ctx, id, GetFilterHeader(r), noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, 404)) return @@ -1234,8 +1271,12 @@ func GetOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] vmId := vars["vmId"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } - vm, err := orchestratorSvc.GetHostVirtualMachine(ctx, id, vmId) + vm, err := orchestratorSvc.GetHostVirtualMachine(ctx, id, vmId, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, 404)) return @@ -1305,8 +1346,12 @@ func GetOrchestratorHostVirtualMachineStatusHandler() restapi.ControllerHandler vars := mux.Vars(r) id := vars["id"] vmId := vars["vmId"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } - response, err := orchestratorSvc.GetHostVirtualMachineStatus(ctx, id, vmId) + response, err := orchestratorSvc.GetHostVirtualMachineStatus(ctx, id, vmId, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1393,6 +1438,10 @@ func SetOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] vmId := vars["vmId"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -1409,7 +1458,7 @@ func SetOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { return } - response, err := orchestratorSvc.ConfigureHostVirtualMachine(ctx, id, vmId, request) + response, err := orchestratorSvc.ConfigureHostVirtualMachine(ctx, id, vmId, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1443,6 +1492,10 @@ func StartOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] vmId := vars["vmId"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } request := models.VirtualMachineConfigRequest{ Operations: []*models.VirtualMachineConfigRequestOperation{ @@ -1453,7 +1506,7 @@ func StartOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { }, } - response, err := orchestratorSvc.ConfigureHostVirtualMachine(ctx, id, vmId, request) + response, err := orchestratorSvc.ConfigureHostVirtualMachine(ctx, id, vmId, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1487,6 +1540,10 @@ func StopOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] vmId := vars["vmId"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } request := models.VirtualMachineConfigRequest{ Operations: []*models.VirtualMachineConfigRequestOperation{ @@ -1497,7 +1554,7 @@ func StopOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { }, } - response, err := orchestratorSvc.ConfigureHostVirtualMachine(ctx, id, vmId, request) + response, err := orchestratorSvc.ConfigureHostVirtualMachine(ctx, id, vmId, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1532,6 +1589,10 @@ func ExecutesOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] vmId := vars["vmId"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -1548,7 +1609,7 @@ func ExecutesOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { return } - response, err := orchestratorSvc.ExecuteOnHostVirtualMachine(ctx, id, vmId, request) + response, err := orchestratorSvc.ExecuteOnHostVirtualMachine(ctx, id, vmId, request, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1778,9 +1839,13 @@ func GetOrchestratorHostReverseProxyHostsHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } orchestratorSvc := orchestrator.NewOrchestratorService(ctx) - response, err := orchestratorSvc.GetHostReverseProxyHosts(ctx, id, "") + response, err := orchestratorSvc.GetHostReverseProxyHosts(ctx, id, "", noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return @@ -1812,9 +1877,13 @@ func GetOrchestratorHostReverseProxyHostHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] reverseProxyHostId := vars["reverse_proxy_host_id"] + noCache := false + if r.Header.Get("X-No-Cache") == "true" { + noCache = true + } orchestratorSvc := orchestrator.NewOrchestratorService(ctx) - response, err := orchestratorSvc.GetHostReverseProxyHost(ctx, id, reverseProxyHostId) + response, err := orchestratorSvc.GetHostReverseProxyHost(ctx, id, reverseProxyHostId, noCache) if err != nil { ReturnApiError(ctx, w, models.NewFromError(err)) return diff --git a/src/data/models/orchestrator_host.go b/src/data/models/orchestrator_host.go index 9b6d8d81..b44826c5 100644 --- a/src/data/models/orchestrator_host.go +++ b/src/data/models/orchestrator_host.go @@ -69,6 +69,15 @@ func (o OrchestratorHost) GetHost() string { return url.String() } +func (o OrchestratorHost) GetHostUrl() string { + url, err := url.Parse(o.Host) + if err != nil { + return "" + } else { + return url.Path + } +} + func (o *OrchestratorHost) SetUnhealthy(reason string) { if o.State == "unhealthy" { return diff --git a/src/data/models/virtual_machine.go b/src/data/models/virtual_machine.go index 006a4353..32d72840 100644 --- a/src/data/models/virtual_machine.go +++ b/src/data/models/virtual_machine.go @@ -4,6 +4,7 @@ import "encoding/json" type VirtualMachine struct { ID string `json:"ID,omitempty"` + HostUrl string `json:"host_url,omitempty"` HostId string `json:"host_id,omitempty"` HostState string `json:"host_state,omitempty"` User string `json:"user,omitempty"` diff --git a/src/mappers/virtual_machine.go b/src/mappers/virtual_machine.go index aafe8a0d..c9795cfd 100644 --- a/src/mappers/virtual_machine.go +++ b/src/mappers/virtual_machine.go @@ -62,6 +62,7 @@ func MapDtoVirtualMachineToApi(m data_models.VirtualMachine) models.ParallelsVM mapped := models.ParallelsVM{ HostId: m.HostId, Host: m.Host, + HostUrl: m.HostUrl, HostState: m.HostState, HostExternalIpAddress: m.HostExternalIpAddress, InternalIpAddress: m.InternalIpAddress, diff --git a/src/models/api_error_response.go b/src/models/api_error_response.go index e4068274..8078352b 100644 --- a/src/models/api_error_response.go +++ b/src/models/api_error_response.go @@ -1,6 +1,8 @@ package models -import "github.com/Parallels/prl-devops-service/errors" +import ( + "github.com/Parallels/prl-devops-service/errors" +) type ApiErrorStack struct { Message string `json:"message"` diff --git a/src/models/virtual_machines.go b/src/models/virtual_machines.go index 0423689b..a5a0aa3c 100644 --- a/src/models/virtual_machines.go +++ b/src/models/virtual_machines.go @@ -5,6 +5,7 @@ type ParallelsVMs []ParallelsVM type ParallelsVM struct { ID string `json:"ID"` Host string `json:"host,omitempty"` + HostUrl string `json:"host_url,omitempty"` HostId string `json:"host_id,omitempty"` HostState string `json:"host_state,omitempty"` HostExternalIpAddress string `json:"host_external_ip_address,omitempty"` diff --git a/src/orchestrator/configure_host_virtual_machine.go b/src/orchestrator/configure_host_virtual_machine.go index b1644edb..c9082802 100644 --- a/src/orchestrator/configure_host_virtual_machine.go +++ b/src/orchestrator/configure_host_virtual_machine.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" data_models "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -8,8 +10,13 @@ import ( "github.com/Parallels/prl-devops-service/models" ) -func (s *OrchestratorService) ConfigureVirtualMachine(ctx basecontext.ApiContext, vmId string, request models.VirtualMachineConfigRequest) (*models.VirtualMachineConfigResponse, error) { - vm, err := s.GetVirtualMachine(ctx, vmId) +func (s *OrchestratorService) ConfigureVirtualMachine(ctx basecontext.ApiContext, vmId string, request models.VirtualMachineConfigRequest, noCache bool) (*models.VirtualMachineConfigResponse, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return nil, err } @@ -34,7 +41,7 @@ func (s *OrchestratorService) ConfigureVirtualMachine(ctx basecontext.ApiContext return nil, errors.NewWithCodef(400, "Host %s is not healthy", host.ID) } - result, err := s.ConfigureHostVirtualMachine(ctx, vm.HostId, vmId, request) + result, err := s.ConfigureHostVirtualMachine(ctx, vm.HostId, vmId, request, false) if err != nil { return nil, err } @@ -42,7 +49,11 @@ func (s *OrchestratorService) ConfigureVirtualMachine(ctx basecontext.ApiContext return result, nil } -func (s *OrchestratorService) ConfigureHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string, request models.VirtualMachineConfigRequest) (*models.VirtualMachineConfigResponse, error) { +func (s *OrchestratorService) ConfigureHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string, request models.VirtualMachineConfigRequest, useCache bool) (*models.VirtualMachineConfigResponse, error) { + if !useCache { + s.Refresh() + } + host, err := s.GetHost(ctx, hostId) if err != nil { return nil, err @@ -60,7 +71,7 @@ func (s *OrchestratorService) ConfigureHostVirtualMachine(ctx basecontext.ApiCon return nil, errors.NewWithCodef(400, "Host %s is not healthy", hostId) } - vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId) + vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId, false) if err != nil { return nil, err } @@ -79,6 +90,7 @@ func (s *OrchestratorService) ConfigureHostVirtualMachine(ctx basecontext.ApiCon func (s *OrchestratorService) CallConfigureHostVirtualMachine(host *data_models.OrchestratorHost, vmId string, request models.VirtualMachineConfigRequest) (*models.VirtualMachineConfigResponse, error) { httpClient := s.getApiClient(*host) + httpClient.WithTimeout(3 * time.Minute) path := "/machines/" + vmId + "/set" url, err := helpers.JoinUrl([]string{host.GetHost(), path}) if err != nil { diff --git a/src/orchestrator/create_host_virtual_machine.go b/src/orchestrator/create_host_virtual_machine.go index 12c18e9f..26f63f5d 100644 --- a/src/orchestrator/create_host_virtual_machine.go +++ b/src/orchestrator/create_host_virtual_machine.go @@ -129,6 +129,7 @@ func (s *OrchestratorService) CreateHosVirtualMachine(ctx basecontext.ApiContext } } + s.Refresh() return &response, apiError } diff --git a/src/orchestrator/delete_host_catalog_cache.go b/src/orchestrator/delete_host_catalog_cache.go index 31de217f..f61d0706 100644 --- a/src/orchestrator/delete_host_catalog_cache.go +++ b/src/orchestrator/delete_host_catalog_cache.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -33,11 +35,13 @@ func (s *OrchestratorService) DeleteHostCatalogCacheItem(ctx basecontext.ApiCont return err } + s.Refresh() return nil } func (s *OrchestratorService) CallDeleteHostCatalogCacheItem(host *models.OrchestratorHost, catalogId string, versionId string) error { httpClient := s.getApiClient(*host) + httpClient.WithTimeout(5 * time.Minute) // sometimes deleting files can take a bit, waiting for 5 minutes path := "/v1/catalog/cache" if catalogId != "" { path = path + "/" + catalogId diff --git a/src/orchestrator/delete_host_virtual_machine.go b/src/orchestrator/delete_host_virtual_machine.go index f5d96c13..0788f8da 100644 --- a/src/orchestrator/delete_host_virtual_machine.go +++ b/src/orchestrator/delete_host_virtual_machine.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -9,7 +11,7 @@ import ( ) func (s *OrchestratorService) DeleteVirtualMachine(ctx basecontext.ApiContext, vmId string) error { - vm, err := s.GetVirtualMachine(ctx, vmId) + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return err } @@ -66,7 +68,7 @@ func (s *OrchestratorService) DeleteHostVirtualMachine(ctx basecontext.ApiContex return errors.NewWithCodef(400, "Host %s is not healthy", hostId) } - vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId) + vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId, false) if err != nil { return err } @@ -85,11 +87,14 @@ func (s *OrchestratorService) DeleteHostVirtualMachine(ctx basecontext.ApiContex return err } + s.Refresh() return nil } func (s *OrchestratorService) CallDeleteHostVirtualMachine(host *models.OrchestratorHost, vmId string) error { httpClient := s.getApiClient(*host) + httpClient.WithTimeout(10 * time.Minute) // deleting virtual machines can take a while, waiting for 10 minutes + path := "/v1/machines/" + vmId url, err := helpers.JoinUrl([]string{host.GetHost(), path}) if err != nil { diff --git a/src/orchestrator/enable_disable_host.go b/src/orchestrator/enable_disable_host.go index 4dc91a85..1a01350e 100644 --- a/src/orchestrator/enable_disable_host.go +++ b/src/orchestrator/enable_disable_host.go @@ -27,6 +27,7 @@ func (s *OrchestratorService) EnableHost(ctx basecontext.ApiContext, hostIdOrHos return nil, err } + s.Refresh() return updatedHost, nil } @@ -50,5 +51,6 @@ func (s *OrchestratorService) DisableHost(ctx basecontext.ApiContext, hostIdOrHo return nil, err } + s.Refresh() return updatedHost, nil } diff --git a/src/orchestrator/enable_host_reverse_proxy.go b/src/orchestrator/enable_host_reverse_proxy.go index 79e0f72b..f577152c 100644 --- a/src/orchestrator/enable_host_reverse_proxy.go +++ b/src/orchestrator/enable_host_reverse_proxy.go @@ -32,6 +32,7 @@ func (s *OrchestratorService) EnableHostReverseProxy(ctx basecontext.ApiContext, return err } + s.Refresh() return nil } diff --git a/src/orchestrator/execute_on_host_virtual_machine.go b/src/orchestrator/execute_on_host_virtual_machine.go index 4df8c409..7dad4357 100644 --- a/src/orchestrator/execute_on_host_virtual_machine.go +++ b/src/orchestrator/execute_on_host_virtual_machine.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" data_models "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -8,8 +10,13 @@ import ( "github.com/Parallels/prl-devops-service/models" ) -func (s *OrchestratorService) ExecuteOnVirtualMachine(ctx basecontext.ApiContext, vmId string, request models.VirtualMachineExecuteCommandRequest) (*models.VirtualMachineExecuteCommandResponse, error) { - vm, err := s.GetVirtualMachine(ctx, vmId) +func (s *OrchestratorService) ExecuteOnVirtualMachine(ctx basecontext.ApiContext, vmId string, request models.VirtualMachineExecuteCommandRequest, noCache bool) (*models.VirtualMachineExecuteCommandResponse, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return nil, err } @@ -32,11 +39,16 @@ func (s *OrchestratorService) ExecuteOnVirtualMachine(ctx basecontext.ApiContext return nil, errors.NewWithCodef(400, "Host %s is not healthy", host.Host) } - return s.ExecuteOnHostVirtualMachine(ctx, vm.HostId, vm.ID, request) + return s.ExecuteOnHostVirtualMachine(ctx, vm.HostId, vm.ID, request, false) } -func (s *OrchestratorService) ExecuteOnHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string, request models.VirtualMachineExecuteCommandRequest) (*models.VirtualMachineExecuteCommandResponse, error) { - vm, err := s.GetVirtualMachine(ctx, vmId) +func (s *OrchestratorService) ExecuteOnHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string, request models.VirtualMachineExecuteCommandRequest, noCache bool) (*models.VirtualMachineExecuteCommandResponse, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return nil, err } @@ -59,7 +71,7 @@ func (s *OrchestratorService) ExecuteOnHostVirtualMachine(ctx basecontext.ApiCon return nil, errors.NewWithCodef(400, "Host %s is not healthy", host.Host) } - currentVmState, err := s.GetHostVirtualMachineStatus(ctx, host.ID, vm.ID) + currentVmState, err := s.GetHostVirtualMachineStatus(ctx, host.ID, vm.ID, false) if err != nil { return nil, err } @@ -74,6 +86,8 @@ func (s *OrchestratorService) ExecuteOnHostVirtualMachine(ctx basecontext.ApiCon func (s *OrchestratorService) CallExecuteOnHostVirtualMachine(host *data_models.OrchestratorHost, vmId string, request models.VirtualMachineExecuteCommandRequest) (*models.VirtualMachineExecuteCommandResponse, error) { httpClient := s.getApiClient(*host) + // TODO: Adding the timeout to be configured by the user + httpClient.WithTimeout(10 * time.Minute) // executing commands on virtual machines can take a while, waiting for 10 minutes path := "/machines/" + vmId + "/execute" url, err := helpers.JoinUrl([]string{host.GetHost(), path}) if err != nil { diff --git a/src/orchestrator/get_host_reverse_proxy_hosts.go b/src/orchestrator/get_host_reverse_proxy_hosts.go index 6260e1f0..24df02a5 100644 --- a/src/orchestrator/get_host_reverse_proxy_hosts.go +++ b/src/orchestrator/get_host_reverse_proxy_hosts.go @@ -7,7 +7,12 @@ import ( "github.com/Parallels/prl-devops-service/serviceprovider" ) -func (s *OrchestratorService) GetHostReverseProxyHosts(ctx basecontext.ApiContext, hostId string, filter string) ([]*models.ReverseProxyHost, error) { +func (s *OrchestratorService) GetHostReverseProxyHosts(ctx basecontext.ApiContext, hostId string, filter string, noCache bool) ([]*models.ReverseProxyHost, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { return nil, err @@ -21,7 +26,12 @@ func (s *OrchestratorService) GetHostReverseProxyHosts(ctx basecontext.ApiContex return hosts, nil } -func (s *OrchestratorService) GetHostReverseProxyHost(ctx basecontext.ApiContext, hostId string, rpHostId string) (*models.ReverseProxyHost, error) { +func (s *OrchestratorService) GetHostReverseProxyHost(ctx basecontext.ApiContext, hostId string, rpHostId string, noCache bool) (*models.ReverseProxyHost, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { return nil, err diff --git a/src/orchestrator/get_host_virtual_machine_status.go b/src/orchestrator/get_host_virtual_machine_status.go index 91a34b0e..25a5435e 100644 --- a/src/orchestrator/get_host_virtual_machine_status.go +++ b/src/orchestrator/get_host_virtual_machine_status.go @@ -8,8 +8,13 @@ import ( "github.com/Parallels/prl-devops-service/models" ) -func (s *OrchestratorService) GetVirtualMachineStatus(ctx basecontext.ApiContext, vmId string) (*models.VirtualMachineStatusResponse, error) { - vm, err := s.GetVirtualMachine(ctx, vmId) +func (s *OrchestratorService) GetVirtualMachineStatus(ctx basecontext.ApiContext, vmId string, noCache bool) (*models.VirtualMachineStatusResponse, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return nil, err } @@ -34,7 +39,7 @@ func (s *OrchestratorService) GetVirtualMachineStatus(ctx basecontext.ApiContext return nil, errors.NewWithCodef(400, "Host %s is not healthy", host.ID) } - result, err := s.GetHostVirtualMachineStatus(ctx, vm.HostId, vmId) + result, err := s.GetHostVirtualMachineStatus(ctx, vm.HostId, vmId, false) if err != nil { return nil, err } @@ -42,7 +47,12 @@ func (s *OrchestratorService) GetVirtualMachineStatus(ctx basecontext.ApiContext return result, nil } -func (s *OrchestratorService) GetHostVirtualMachineStatus(ctx basecontext.ApiContext, hostId string, vmId string) (*models.VirtualMachineStatusResponse, error) { +func (s *OrchestratorService) GetHostVirtualMachineStatus(ctx basecontext.ApiContext, hostId string, vmId string, noCache bool) (*models.VirtualMachineStatusResponse, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + host, err := s.GetHost(ctx, hostId) if err != nil { return nil, err @@ -60,7 +70,7 @@ func (s *OrchestratorService) GetHostVirtualMachineStatus(ctx basecontext.ApiCon return nil, errors.NewWithCodef(400, "Host %s is not healthy", hostId) } - vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId) + vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId, false) if err != nil { return nil, err } diff --git a/src/orchestrator/get_host_virtual_machines.go b/src/orchestrator/get_host_virtual_machines.go index dc6faba1..1dd3135f 100644 --- a/src/orchestrator/get_host_virtual_machines.go +++ b/src/orchestrator/get_host_virtual_machines.go @@ -11,7 +11,11 @@ import ( "github.com/Parallels/prl-devops-service/serviceprovider" ) -func (s *OrchestratorService) GetHostVirtualMachines(ctx basecontext.ApiContext, hostId string, filter string) ([]*models.VirtualMachine, error) { +func (s *OrchestratorService) GetHostVirtualMachines(ctx basecontext.ApiContext, hostId string, filter string, noCache bool) ([]*models.VirtualMachine, error) { + if noCache { + s.Refresh() + } + dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { return nil, err @@ -45,7 +49,11 @@ func (s *OrchestratorService) GetHostVirtualMachines(ctx basecontext.ApiContext, return result, nil } -func (s *OrchestratorService) GetHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string) (*models.VirtualMachine, error) { +func (s *OrchestratorService) GetHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string, noCache bool) (*models.VirtualMachine, error) { + if noCache { + s.Refresh() + } + dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { return nil, err diff --git a/src/orchestrator/get_hosts.go b/src/orchestrator/get_hosts.go index cbe57642..cb4d3859 100644 --- a/src/orchestrator/get_hosts.go +++ b/src/orchestrator/get_hosts.go @@ -32,16 +32,16 @@ func (s *OrchestratorService) GetHosts(ctx basecontext.ApiContext, filter string starTime := time.Now() go func(host models.OrchestratorHost) { defer wg.Done() - ctx.LogDebugf("Processing Host: %v", host.Host) + ctx.LogDebugf("[Orchestrator] Processing Host: %v", host.Host) if host.Enabled { host.State = s.GetHostHealthCheckState(&host) - ctx.LogDebugf("Host State: %v", host.State) + ctx.LogDebugf("[Orchestrator] Host %v state: %v", host.Host, host.State) } mutex.Lock() result = append(result, &host) mutex.Unlock() - ctx.LogDebugf("Processing Host: %v - Time: %v", host.Host, time.Since(starTime)) + ctx.LogDebugf("[Orchestrator] Processing Host: %v - Time: %v", host.Host, time.Since(starTime)) }(host) } diff --git a/src/orchestrator/get_orchestrator_resources.go b/src/orchestrator/get_orchestrator_resources.go index 7d9d95ed..47f80276 100644 --- a/src/orchestrator/get_orchestrator_resources.go +++ b/src/orchestrator/get_orchestrator_resources.go @@ -6,7 +6,12 @@ import ( "github.com/Parallels/prl-devops-service/serviceprovider" ) -func (s *OrchestratorService) GetResources(ctx basecontext.ApiContext) ([]models.HostResourceOverviewResponseItem, error) { +func (s *OrchestratorService) GetResources(ctx basecontext.ApiContext, noCache bool) ([]models.HostResourceOverviewResponseItem, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { return nil, err diff --git a/src/orchestrator/get_virtual_machines.go b/src/orchestrator/get_virtual_machines.go index b6fdb74f..dffe673f 100644 --- a/src/orchestrator/get_virtual_machines.go +++ b/src/orchestrator/get_virtual_machines.go @@ -10,7 +10,12 @@ import ( "github.com/Parallels/prl-devops-service/serviceprovider" ) -func (s *OrchestratorService) GetVirtualMachines(ctx basecontext.ApiContext, filter string) ([]models.VirtualMachine, error) { +func (s *OrchestratorService) GetVirtualMachines(ctx basecontext.ApiContext, filter string, noCache bool) ([]models.VirtualMachine, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { return nil, err @@ -44,7 +49,12 @@ func (s *OrchestratorService) GetVirtualMachines(ctx basecontext.ApiContext, fil return result, nil } -func (s *OrchestratorService) GetVirtualMachine(ctx basecontext.ApiContext, idOrName string) (*models.VirtualMachine, error) { +func (s *OrchestratorService) GetVirtualMachine(ctx basecontext.ApiContext, idOrName string, noCache bool) (*models.VirtualMachine, error) { + if noCache { + ctx.LogDebugf("[Orchestrator] No cache set, refreshing all hosts...") + s.Refresh() + } + retryCount := 0 var resultVm *models.VirtualMachine diff --git a/src/orchestrator/main.go b/src/orchestrator/main.go index 7315fe26..e52121c0 100644 --- a/src/orchestrator/main.go +++ b/src/orchestrator/main.go @@ -205,6 +205,7 @@ func (s *OrchestratorService) processHost(host models.OrchestratorHost) { dtoVm := mappers.MapDtoVirtualMachineFromApi(vm) dtoVm.HostId = host.ID dtoVm.Host = host.GetHost() + dtoVm.HostUrl = host.GetHostUrl() host.VirtualMachines = append(host.VirtualMachines, dtoVm) } diff --git a/src/orchestrator/register_host.go b/src/orchestrator/register_host.go index e4938620..bc653e2e 100644 --- a/src/orchestrator/register_host.go +++ b/src/orchestrator/register_host.go @@ -23,5 +23,6 @@ func (s *OrchestratorService) RegisterHost(ctx basecontext.ApiContext, host *mod return nil, err } + s.Refresh() return dbHost, nil } diff --git a/src/orchestrator/register_host_virtual_machine.go b/src/orchestrator/register_host_virtual_machine.go index 16080e8a..b31c0792 100644 --- a/src/orchestrator/register_host_virtual_machine.go +++ b/src/orchestrator/register_host_virtual_machine.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" data_models "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -29,6 +31,7 @@ func (s *OrchestratorService) RegisterHostVirtualMachine(ctx basecontext.ApiCont func (s *OrchestratorService) CallRegisterHostVirtualMachine(host *data_models.OrchestratorHost, request models.RegisterVirtualMachineRequest) (*models.ParallelsVM, error) { httpClient := s.getApiClient(*host) + httpClient.WithTimeout(2 * time.Minute) path := "/machines/register" url, err := helpers.JoinUrl([]string{host.GetHost(), path}) if err != nil { diff --git a/src/orchestrator/rename_host_virtual_machine.go b/src/orchestrator/rename_host_virtual_machine.go index ddd33f9d..f3847c81 100644 --- a/src/orchestrator/rename_host_virtual_machine.go +++ b/src/orchestrator/rename_host_virtual_machine.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" data_models "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -9,7 +11,7 @@ import ( ) func (s *OrchestratorService) RenameVirtualMachine(ctx basecontext.ApiContext, vmId string, request models.RenameVirtualMachineRequest) (*models.ParallelsVM, error) { - vm, err := s.GetVirtualMachine(ctx, vmId) + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return nil, err } @@ -60,7 +62,7 @@ func (s *OrchestratorService) RenameHostVirtualMachine(ctx basecontext.ApiContex return nil, errors.NewWithCodef(400, "Host %s is not healthy", hostId) } - vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId) + vm, err := s.GetHostVirtualMachine(ctx, hostId, vmId, false) if err != nil { return nil, err } @@ -79,6 +81,7 @@ func (s *OrchestratorService) RenameHostVirtualMachine(ctx basecontext.ApiContex func (s *OrchestratorService) CallRenameHostVirtualMachine(host *data_models.OrchestratorHost, vmId string, request models.RenameVirtualMachineRequest) (*models.ParallelsVM, error) { httpClient := s.getApiClient(*host) + httpClient.WithTimeout(2 * time.Minute) path := "/machines/" + vmId + "/rename" url, err := helpers.JoinUrl([]string{host.GetHost(), path}) if err != nil { diff --git a/src/orchestrator/unregister_host.go b/src/orchestrator/unregister_host.go index d75279dc..cf9e0556 100644 --- a/src/orchestrator/unregister_host.go +++ b/src/orchestrator/unregister_host.go @@ -16,5 +16,6 @@ func (s *OrchestratorService) UnregisterHost(ctx basecontext.ApiContext, hostId return err } + s.Refresh() return nil } diff --git a/src/orchestrator/unregister_host_virtual_machine.go b/src/orchestrator/unregister_host_virtual_machine.go index 8fa3fd8b..1e75ddb5 100644 --- a/src/orchestrator/unregister_host_virtual_machine.go +++ b/src/orchestrator/unregister_host_virtual_machine.go @@ -1,6 +1,8 @@ package orchestrator import ( + "time" + "github.com/Parallels/prl-devops-service/basecontext" data_models "github.com/Parallels/prl-devops-service/data/models" "github.com/Parallels/prl-devops-service/errors" @@ -9,7 +11,7 @@ import ( ) func (s *OrchestratorService) UnregisterHostVirtualMachine(ctx basecontext.ApiContext, hostId string, vmId string, request models.UnregisterVirtualMachineRequest) (*models.ParallelsVM, error) { - vm, err := s.GetVirtualMachine(ctx, vmId) + vm, err := s.GetVirtualMachine(ctx, vmId, false) if err != nil { return nil, err } @@ -38,6 +40,7 @@ func (s *OrchestratorService) UnregisterHostVirtualMachine(ctx basecontext.ApiCo func (s *OrchestratorService) CallUnregisterHostVirtualMachine(host *data_models.OrchestratorHost, vmId string, request models.UnregisterVirtualMachineRequest) (*models.ParallelsVM, error) { httpClient := s.getApiClient(*host) + httpClient.WithTimeout(3 * time.Minute) path := "/machines/" + vmId + "/unregister" url, err := helpers.JoinUrl([]string{host.GetHost(), path}) if err != nil { diff --git a/src/reverse_proxy/main.go b/src/reverse_proxy/main.go index 12c58a81..56553666 100644 --- a/src/reverse_proxy/main.go +++ b/src/reverse_proxy/main.go @@ -26,10 +26,25 @@ import ( var globalReverseProxyService *ReverseProxyService +type reverseProxyOperationRequest struct { + operation func() error + result chan error +} + +type reverseProxyServiceState int + +const ( + ReverseProxyServiceStateStopped reverseProxyServiceState = iota + ReverseProxyServiceStateStarting + ReverseProxyServiceStateStarted + ReverseProxyServiceStateStopping +) + type ReverseProxyService struct { enabled bool host string port string + State reverseProxyServiceState forwarding_hosts []*data_models.ReverseProxyHost db *data.JsonDatabase api_ctx basecontext.ApiContext @@ -38,6 +53,9 @@ type ReverseProxyService struct { tcpListeners []net.Listener httpListeners []*http.Server wg *sync.WaitGroup + + opQueue chan reverseProxyOperationRequest + queueOnce sync.Once } func Get(ctx basecontext.ApiContext) *ReverseProxyService { @@ -50,7 +68,10 @@ func Get(ctx basecontext.ApiContext) *ReverseProxyService { func GetConfig() models.ReverseProxyConfig { cfg := config.Get() - result := models.ReverseProxyConfig{} + result := models.ReverseProxyConfig{ + Enabled: false, + } + if cfg == nil || globalReverseProxyService == nil { result.Enabled = false } @@ -60,8 +81,8 @@ func GetConfig() models.ReverseProxyConfig { result.Enabled = cfg.IsReverseProxyEnabled() // Returning db data if available and not different from the config - dtoRp, _ := globalReverseProxyService.db.GetReverseProxyConfig(globalReverseProxyService.api_ctx) - if dtoRp != nil { + dtoRp, err := globalReverseProxyService.db.GetReverseProxyConfig(globalReverseProxyService.api_ctx) + if dtoRp != nil && err == nil { if !dtoRp.Diff(mappers.ConfigReverseProxyToDto(result)) { result.Host = dtoRp.Host result.Port = dtoRp.Port @@ -88,6 +109,7 @@ func New(ctx basecontext.ApiContext) *ReverseProxyService { tcpListeners: []net.Listener{}, httpListeners: []*http.Server{}, wg: &sync.WaitGroup{}, + State: ReverseProxyServiceStateStopped, } return globalReverseProxyService @@ -229,7 +251,13 @@ func (rps *ReverseProxyService) LoadFromDb() error { if hostCopy.TcpRoute != nil && hostCopy.TcpRoute.TargetVmId != "" { vm, err := prl_svc.GetVm(rps.api_ctx, hostCopy.TcpRoute.TargetVmId) if err != nil || vm.InternalIpAddress == "" || vm.InternalIpAddress == "-" || vm.State != "running" { - rps.api_ctx.LogErrorf("Error getting vm %s for reverse proxy tcp route: %s", hostCopy.TcpRoute.TargetVmId, err) + if err != nil { + rps.api_ctx.LogErrorf("Error getting vm %s for reverse proxy tcp route: %s", hostCopy.TcpRoute.TargetVmId, err) + } else if vm == nil { + rps.api_ctx.LogErrorf("Error getting vm %s for reverse proxy tcp route: vm could not be found", hostCopy.TcpRoute.TargetVmId) + } else if vm.InternalIpAddress == "" || vm.InternalIpAddress == "-" { + rps.api_ctx.LogErrorf("Error getting vm %s for reverse proxy tcp route: vm internal ip address is empty", hostCopy.TcpRoute.TargetVmId) + } hostCopy.TcpRoute.TargetHost = "---" } else { hostCopy.TcpRoute.TargetHost = vm.InternalIpAddress @@ -243,6 +271,22 @@ func (rps *ReverseProxyService) LoadFromDb() error { } func (rps *ReverseProxyService) Start() error { + rps.initQueue() + resultChan := make(chan error) + rps.opQueue <- reverseProxyOperationRequest{ + operation: rps.startInternal, + result: resultChan, + } + return <-resultChan +} + +func (rps *ReverseProxyService) startInternal() error { + if rps.State == ReverseProxyServiceStateStarted || rps.State == ReverseProxyServiceStateStarting { + rps.api_ctx.LogInfof("[Reverse Proxy] Reverse proxy service already started") + return nil + } + + rps.State = ReverseProxyServiceStateStarting cfg := config.Get() rps.ctx, rps.cancelFunc = context.WithCancel(context.Background()) errorChan := make(chan error, 1) @@ -289,10 +333,29 @@ func (rps *ReverseProxyService) Start() error { default: } + rps.api_ctx.LogInfof("[Reverse Proxy] Reverse proxy started") + rps.State = ReverseProxyServiceStateStarted return nil } func (rps *ReverseProxyService) Stop() error { + rps.initQueue() + resultChan := make(chan error) + rps.opQueue <- reverseProxyOperationRequest{ + operation: rps.stopInternal, + result: resultChan, + } + return <-resultChan +} + +func (rps *ReverseProxyService) stopInternal() error { + if rps.State == ReverseProxyServiceStateStopped || rps.State == ReverseProxyServiceStateStopping { + rps.api_ctx.LogInfof("[Reverse Proxy] Reverse proxy service already stopped") + return nil + } + + rps.State = ReverseProxyServiceStateStopping + rps.api_ctx.LogInfof("[Reverse Proxy] Stopping reverse proxy service") // fixing a possible issue with the cancel function not being called if rps.cancelFunc != nil { @@ -300,6 +363,7 @@ func (rps *ReverseProxyService) Stop() error { } for _, listener := range rps.tcpListeners { + // Check if the listener is already closed rps.api_ctx.LogInfof("[Reverse Proxy] [TCP Route] Closing listener") if err := listener.Close(); err != nil { rps.api_ctx.LogErrorf("[Reverse Proxy] [TCP Route] Error closing listener: %s", err) @@ -311,9 +375,14 @@ func (rps *ReverseProxyService) Stop() error { defer cancel() for _, server := range rps.httpListeners { + rps.api_ctx.LogInfof("[Reverse Proxy] [HTTP Route] Closing server") if err := server.Shutdown(ctxShutdown); err != nil { - rps.api_ctx.LogErrorf("[Reverse Proxy] [HTTP Route] Error shutting down server: %s", err) + if err == http.ErrServerClosed { + rps.api_ctx.LogInfof("[Reverse Proxy] [HTTP Route] Server already closed") + } else { + rps.api_ctx.LogErrorf("[Reverse Proxy] [HTTP Route] Error shutting down server: %s", err) + } } rps.api_ctx.LogInfof("[Reverse Proxy] [HTTP Route] Server closed") @@ -324,17 +393,28 @@ func (rps *ReverseProxyService) Stop() error { rps.wg.Wait() rps.api_ctx.LogInfof("[Reverse Proxy] Reverse proxy service stopped") + rps.State = ReverseProxyServiceStateStopped return nil } func (rps *ReverseProxyService) Restart() error { - if err := rps.Stop(); err != nil { + rps.initQueue() + resultChan := make(chan error) + rps.opQueue <- reverseProxyOperationRequest{ + operation: rps.restartInternal, + result: resultChan, + } + return <-resultChan +} + +func (rps *ReverseProxyService) restartInternal() error { + if err := rps.stopInternal(); err != nil { return err } done := make(chan error, 1) go func() { - done <- rps.Start() + done <- rps.startInternal() }() return <-done @@ -498,6 +578,7 @@ func (rps *ReverseProxyService) listenHttpRoute(host *data_models.ReverseProxyHo target = fmt.Sprintf("%s:%s", host.Host, host.Port) } + // TODO: Add the ability to have a rewrite of the path URL and the schema if strings.EqualFold(target, req.Host) { for _, route := range host.HttpRoutes { if route.TargetHost == "" || route.TargetHost == "---" { @@ -630,6 +711,20 @@ func (rps *ReverseProxyService) copyWithContext(ctx context.Context, dst io.Writ } } +func (rps *ReverseProxyService) initQueue() { + rps.queueOnce.Do(func() { + rps.opQueue = make(chan reverseProxyOperationRequest, 100) + go rps.processQueue() + }) +} + +func (rps *ReverseProxyService) processQueue() { + for req := range rps.opQueue { + err := req.operation() + req.result <- err + } +} + func newReverseProxy(target string) *httputil.ReverseProxy { url, _ := url.Parse(target) return httputil.NewSingleHostReverseProxy(url) diff --git a/src/security/brute_force_guard/main.go b/src/security/brute_force_guard/main.go index cfa63e05..78726703 100644 --- a/src/security/brute_force_guard/main.go +++ b/src/security/brute_force_guard/main.go @@ -142,13 +142,13 @@ func (s *BruteForceGuard) processEnvironmentVariables() { if err != nil { s.ctx.LogWarnf("[BruteForceGuard] Invalid value for %s: %s", constants.BRUTE_FORCE_MAX_LOGIN_ATTEMPTS_ENV_VAR, err.Error()) } else { - s.ctx.LogDebugf("[BruteForceGuard] Setting %s to %d", constants.BRUTE_FORCE_MAX_LOGIN_ATTEMPTS_ENV_VAR, maxLoginAttempts) + s.ctx.LogDebugf("[BruteForceGuard] Setting %s to %v", constants.BRUTE_FORCE_MAX_LOGIN_ATTEMPTS_ENV_VAR, maxLoginAttempts) s.options.WithMaxLoginAttempts(maxLoginAttempts) } } if cfg.GetKey(constants.BRUTE_FORCE_LOCKOUT_DURATION_ENV_VAR) != "" { - s.ctx.LogDebugf("[BruteForceGuard] Setting %s to %s", constants.BRUTE_FORCE_LOCKOUT_DURATION_ENV_VAR, cfg.GetKey(constants.BRUTE_FORCE_LOCKOUT_DURATION_ENV_VAR)) + s.ctx.LogDebugf("[BruteForceGuard] Setting %s to %v", constants.BRUTE_FORCE_LOCKOUT_DURATION_ENV_VAR, cfg.GetKey(constants.BRUTE_FORCE_LOCKOUT_DURATION_ENV_VAR)) s.options.WithBlockDuration(cfg.GetKey(constants.BRUTE_FORCE_LOCKOUT_DURATION_ENV_VAR)) } diff --git a/src/serviceprovider/system/main.go b/src/serviceprovider/system/main.go index fe086ceb..82f14efb 100644 --- a/src/serviceprovider/system/main.go +++ b/src/serviceprovider/system/main.go @@ -105,7 +105,7 @@ func (s *SystemService) Installed() bool { func (s *SystemService) GetSystemUsers(ctx basecontext.ApiContext) ([]models.SystemUser, error) { if s.cache.SystemUsers != nil && len(s.cache.SystemUsers) > 0 { - ctx.LogDebugf("Returning cached system users") + ctx.LogDebugf("[SYSTEM] Returning cached system users") return s.cache.SystemUsers, nil } @@ -385,7 +385,7 @@ func (s *SystemService) getUserIdWindows(ctx basecontext.ApiContext, user string func (s *SystemService) GetCurrentUser(ctx basecontext.ApiContext) (string, error) { if s.cache.CurrentUser != "" { - ctx.LogDebugf("Returning cached current user") + ctx.LogDebugf("[SYSTEM] Returning cached current user") return s.cache.CurrentUser, nil } @@ -439,7 +439,7 @@ func (s *SystemService) getWindowsCurrentUser(ctx basecontext.ApiContext) (strin func (s *SystemService) GetUniqueId(ctx basecontext.ApiContext) (string, error) { if s.cache.UniqueId != "" { - ctx.LogDebugf("Returning cached unique id") + ctx.LogDebugf("[SYSTEM] Returning cached unique id") return s.cache.UniqueId, nil } @@ -552,7 +552,7 @@ func (s *SystemService) changeLinuxFileUserOwner(userName string, filePath strin func (s *SystemService) GetHardwareInfo(ctx basecontext.ApiContext) (*models.SystemHardwareInfo, error) { if s.cache.HardwareInfo != nil { - ctx.LogDebugf("Returning cached hardware info") + ctx.LogDebugf("[SYSTEM] Returning cached hardware info") return s.cache.HardwareInfo, nil } @@ -670,7 +670,7 @@ func (s *SystemService) parseDfCommand(output string) (totalDisk float64, freeDi func (s *SystemService) GetArchitecture(ctx basecontext.ApiContext) (string, error) { if s.cache.Architecture != "" { - ctx.LogDebugf("Returning cached architecture") + ctx.LogDebugf("[SYSTEM] Returning cached architecture") return s.cache.Architecture, nil } @@ -758,7 +758,7 @@ func (s *SystemService) getUniversalExternalIp(ctx basecontext.ApiContext) (stri if s.cache.ExternalIpAddress != "" && s.cache.LastUpdatedExternalIpAddress > 0 { currentTime := time.Now().Unix() if currentTime-s.cache.LastUpdatedExternalIpAddress < 2*3600 { - ctx.LogDebugf("Returning cached external IP address") + ctx.LogDebugf("[SYSTEM] Returning cached external IP address") return s.cache.ExternalIpAddress, nil } }