Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding the orchestrator #30

Merged
merged 1 commit into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@
// "args": [
// "--port=5570",
// ]
},
{
"name": "Test Parallels Desktop Api Catalog Providers",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/src/main.go",
"envFile": "${workspaceFolder}/.env",
"args": [
"--test",
"--catalog-providers",
]
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions docs/orchestrator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Parallels Desktop Orchestrator Service

The Parallels Desktop Orchestrator Service is a service that can run in a container or directly in a host and will allow you to orchestrate and manage multiple Parallels Desktop Api Services. This will allow in a simple way to have a single pane of glass to manage multiple Parallels Desktop Api Services and check their status.
You will also be able to create virtual machines automatically and let the orchestrator choose the host with enough free resources

## Architecture

The Parallels Desktop Orchestrator Service is written in go and uses the same base code as the Parallels Desktop Api Service. One single executable that depending on how you run it it will behave in a different way, this allows for a simpler way for deploying it.
The service itself has a simple architecture where it will have a collection of hosts, these hosts are connectors to a Parallels Desktop API instance, the orchestrator then will start by having a background service that keeps an eye on the status of each host and records any changes that it sees, like for example the available resources, it's health state and the virtual machines that are running on it.
You can then manage each host individually by creating, starting, stopping and deleting virtual machines or you can let the orchestrator do it for you by creating a virtual machine and letting the orchestrator choose the host with enough resources to run it.

![Orchestrator Architecture](./images/orchestrator_simple_diagram.drawio.png)
13 changes: 4 additions & 9 deletions src/catalog/common.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
package catalog

import (
"github.com/Parallels/pd-api-service/basecontext"
"github.com/Parallels/pd-api-service/catalog/models"
"github.com/Parallels/pd-api-service/serviceprovider/httpclient"
"github.com/Parallels/pd-api-service/serviceprovider/apiclient"
)

func GetAuthenticator(ctx basecontext.ApiContext, provider *models.CatalogManifestProvider) (*httpclient.HttpClientAuthorization, error) {
auth, err := httpclient.GetAuthenticator(ctx, provider.GetUrl(), &httpclient.AuthorizationModel{
func GetAuthenticator(provider *models.CatalogManifestProvider) apiclient.HttpClientServiceAuthorization {
auth := apiclient.HttpClientServiceAuthorization{
Username: provider.Username,
Password: provider.Password,
ApiKey: provider.ApiKey,
})

if err != nil {
return nil, err
}

return auth, nil
return auth
}
14 changes: 5 additions & 9 deletions src/catalog/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/Parallels/pd-api-service/mappers"
api_models "github.com/Parallels/pd-api-service/models"
"github.com/Parallels/pd-api-service/serviceprovider"
"github.com/Parallels/pd-api-service/serviceprovider/httpclient"
"github.com/Parallels/pd-api-service/serviceprovider/apiclient"

"github.com/cjlapao/common-go/helper"
"github.com/cjlapao/common-go/helper/http_helper"
Expand All @@ -27,7 +27,7 @@ func (s *CatalogManifestService) Pull(ctx basecontext.ApiContext, r *models.Pull
foundProvider := false
response := models.NewPullCatalogManifestResponse()
response.MachineName = r.MachineName
httpClient := httpclient.NewHttpCaller()
apiClient := apiclient.NewHttpClient(ctx)
serviceProvider := serviceprovider.Get()
parallelsDesktopSvc := serviceProvider.ParallelsDesktopService
db := serviceProvider.JsonDatabase
Expand Down Expand Up @@ -75,16 +75,12 @@ func (s *CatalogManifestService) Pull(ctx basecontext.ApiContext, r *models.Pull
ctx.LogInfo("Checking if the manifest exists in the remote catalog")
manifest = &models.VirtualMachineCatalogManifest{}
manifest.Provider = &provider
auth, err := GetAuthenticator(ctx, manifest.Provider)
if err != nil {
ctx.LogError("Error getting authenticator for provider %v: %v", manifest.Provider, err)
response.AddError(err)
return response
}
apiClient.SetAuthorization(GetAuthenticator(manifest.Provider))

var catalogManifest api_models.CatalogManifest
path := http_helper.JoinUrl(constants.DEFAULT_API_PREFIX, "catalog", helpers.NormalizeStringUpper(r.CatalogId), helpers.NormalizeString(r.Version), "download")
if clientResponse, err := httpClient.Get(ctx, fmt.Sprintf("%s%s", manifest.Provider.GetUrl(), path), nil, auth, &catalogManifest); err != nil {
getUrl := fmt.Sprintf("%s%s", manifest.Provider.GetUrl(), path)
if clientResponse, err := apiClient.Get(getUrl, &catalogManifest); err != nil {
if clientResponse != nil && clientResponse.ApiError != nil {
if clientResponse.StatusCode == 403 || clientResponse.StatusCode == 400 {
ctx.LogError("Error getting catalog manifest %v: %v", path, clientResponse.ApiError.Message)
Expand Down
22 changes: 7 additions & 15 deletions src/catalog/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/Parallels/pd-api-service/mappers"
api_models "github.com/Parallels/pd-api-service/models"
"github.com/Parallels/pd-api-service/serviceprovider"
"github.com/Parallels/pd-api-service/serviceprovider/httpclient"
"github.com/Parallels/pd-api-service/serviceprovider/apiclient"

"github.com/cjlapao/common-go/helper"
"github.com/cjlapao/common-go/helper/http_helper"
Expand All @@ -34,7 +34,7 @@ func (s *CatalogManifestService) Push(ctx basecontext.ApiContext, r *models.Push
if check {
executed = true
manifest.CleanupRequest.RemoteStorageService = rs
httpClient := httpclient.NewHttpCaller()
apiClient := apiclient.NewHttpClient(ctx)

if err := manifest.Provider.Parse(r.Connection); err != nil {
ctx.LogError("Error parsing provider %v: %v", r.Connection, err)
Expand All @@ -44,12 +44,7 @@ func (s *CatalogManifestService) Push(ctx basecontext.ApiContext, r *models.Push

if manifest.Provider.IsRemote() {
ctx.LogDebug("Testing remote provider %v", manifest.Provider.Host)
_, err := GetAuthenticator(ctx, manifest.Provider)
if err != nil {
ctx.LogError("Error getting authenticator for provider %v: %v", manifest.Provider, err)
manifest.AddError(err)
break
}
apiClient.SetAuthorization(GetAuthenticator(manifest.Provider))
}

// Generating the manifest content
Expand Down Expand Up @@ -248,15 +243,12 @@ func (s *CatalogManifestService) Push(ctx basecontext.ApiContext, r *models.Push
if !manifest.HasErrors() {
if manifest.Provider.IsRemote() {
ctx.LogInfo("Manifest pushed successfully, adding it to the remote database")
auth, err := GetAuthenticator(ctx, manifest.Provider)
if err != nil {
ctx.LogError("Error getting authenticator for provider %v: %v", manifest.Provider, err)
manifest.AddError(err)
break
}
apiClient.SetAuthorization(GetAuthenticator(manifest.Provider))
path := http_helper.JoinUrl(constants.DEFAULT_API_PREFIX, "catalog")

var response api_models.CatalogManifest
if _, err := httpClient.Post(ctx, fmt.Sprintf("%s%s", manifest.Provider.GetUrl(), path), nil, manifest, auth, &response); err != nil {
postUrl := fmt.Sprintf("%s%s", manifest.Provider.GetUrl(), path)
if _, err := apiClient.Post(postUrl, manifest, &response); err != nil {
ctx.LogError("Error posting catalog manifest %v: %v", manifest.Provider.String(), err)
manifest.AddError(err)
break
Expand Down
1 change: 1 addition & 0 deletions src/common/controllers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package common
67 changes: 65 additions & 2 deletions src/config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import (
"github.com/cjlapao/common-go/security"
)

type Config struct{}
type Config struct {
mode string
includeOwnResources bool
}

func NewConfig() *Config {
return &Config{}
return &Config{
mode: "api",
}
}

func (c *Config) GetApiPort() string {
Expand Down Expand Up @@ -198,3 +203,61 @@ func (c *Config) IsCatalogCachingEnable() bool {

return true
}

func (c *Config) GetSystemMode() string {
c.mode = os.Getenv(constants.MODE_ENV_VAR)
if c.mode != "" {
return c.mode
}

c.mode = helper.GetFlagValue(constants.MODE_FLAG, "unknown")
if c.mode == "" {
c.mode = "api"
}

return c.mode
}

func (c *Config) GetLocalhost() string {
schema := "http"
host := "localhost"
port := c.GetApiPort()
if c.TLSEnabled() {
schema = "https"
port = c.GetTLSPort()
}

return schema + "://" + host + ":" + port
}

func (c *Config) IsOrchestrator() bool {
return c.GetSystemMode() == constants.ORCHESTRATOR_MODE
}

func (c *Config) IsCatalog() bool {
return c.GetSystemMode() == constants.CATALOG_MODE
}

func (c *Config) IsApi() bool {
return c.GetSystemMode() == constants.API_MODE
}

func (c *Config) UseOrchestratorResources() bool {
ownResources := os.Getenv(constants.USE_ORCHESTRATOR_RESOURCES_ENV_VAR)
if ownResources != "" {
if ownResources == "true" || ownResources == "1" {
c.includeOwnResources = true
return true
}
}

ownResources = helper.GetFlagValue(constants.USE_ORCHESTRATOR_RESOURCES_FLAG, "unknown")
if ownResources != "" {
if ownResources == "true" || ownResources == "1" {
c.includeOwnResources = true
return true
}
}

return false
}
49 changes: 29 additions & 20 deletions src/constants/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,41 @@ const (
CURRENT_USER_ENV_VAR = "PD_CURRENT_USER"
DEFAULT_TOKEN_DURATION_MINUTES = 60
DEFAULT_CATALOG_CACHE_FOLDER = "./catalog_cache"

API_MODE = "api"
CLI_MODE = "cli"
ORCHESTRATOR_MODE = "orchestrator"
CATALOG_MODE = "catalog"
)

const (
HMAC_SECRET_ENV_VAR = "HMAC_SECRET"
LOG_LEVEL_ENV_VAR = "LOG_LEVEL"
SECURITY_KEY_ENV_VAR = "SECURITY_PRIVATE_KEY"
TLS_ENABLED_ENV_VAR = "TLS_ENABLED"
TLS_PORT_ENV_VAR = "TLS_PORT"
TLS_CERTIFICATE_ENV_VAR = "TLS_CERTIFICATE"
TLS_PRIVATE_KEY_ENV_VAR = "TLS_PRIVATE_KEY"
API_PORT_ENV_VAR = "API_PORT"
API_PREFIX_ENV_VAR = "API_PREFIX"
ROOT_PASSWORD_ENV_VAR = "ROOT_PASSWORD"
DISABLE_CATALOG_CACHING_ENV_VAR = "DISABLE_CATALOG_CACHING"
TOKEN_DURATION_MINUTES_ENV_VAR = "TOKEN_DURATION_MINUTES"
HMAC_SECRET_ENV_VAR = "HMAC_SECRET"
LOG_LEVEL_ENV_VAR = "LOG_LEVEL"
SECURITY_KEY_ENV_VAR = "SECURITY_PRIVATE_KEY"
TLS_ENABLED_ENV_VAR = "TLS_ENABLED"
TLS_PORT_ENV_VAR = "TLS_PORT"
TLS_CERTIFICATE_ENV_VAR = "TLS_CERTIFICATE"
TLS_PRIVATE_KEY_ENV_VAR = "TLS_PRIVATE_KEY"
API_PORT_ENV_VAR = "API_PORT"
API_PREFIX_ENV_VAR = "API_PREFIX"
ROOT_PASSWORD_ENV_VAR = "ROOT_PASSWORD"
DISABLE_CATALOG_CACHING_ENV_VAR = "DISABLE_CATALOG_CACHING"
TOKEN_DURATION_MINUTES_ENV_VAR = "TOKEN_DURATION_MINUTES"
MODE_ENV_VAR = "MODE"
USE_ORCHESTRATOR_RESOURCES_ENV_VAR = "USE_ORCHESTRATOR_RESOURCES"
)

const (
API_PORT_FLAG = "port"
UPDATE_ROOT_PASSWORD_FLAG = "update-root-pass"
GENERATE_SECURITY_KEY_FLAG = "gen-rsa"
FILE_FLAG = "file"
INSTALL_SERVICE_FLAG = "install"
UNINSTALL_SERVICE_FLAG = "uninstall"
TEST_FLAG = "test"
TEST_CATALOG_PROVIDERS_FLAG = "catalog-providers"
TEST_FLAG = "test"
TEST_CATALOG_PROVIDERS_FLAG = "catalog-providers"
API_PORT_FLAG = "port"
UPDATE_ROOT_PASSWORD_FLAG = "update-root-pass"
GENERATE_SECURITY_KEY_FLAG = "gen-rsa"
FILE_FLAG = "file"
INSTALL_SERVICE_FLAG = "install"
UNINSTALL_SERVICE_FLAG = "uninstall"
MODE_FLAG = "mode"
USE_ORCHESTRATOR_RESOURCES_FLAG = "use-orchestrator-resources"
)

const (
Expand Down
13 changes: 8 additions & 5 deletions src/controllers/api_key.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package controllers

import (
"encoding/base64"
"encoding/json"
"net/http"

Expand All @@ -9,6 +10,7 @@ import (
"github.com/Parallels/pd-api-service/mappers"
"github.com/Parallels/pd-api-service/models"
"github.com/Parallels/pd-api-service/restapi"
"github.com/Parallels/pd-api-service/serviceprovider"

"github.com/cjlapao/common-go/helper/http_helper"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -69,7 +71,7 @@ func registerApiKeysHandlers(ctx basecontext.ApiContext, version string) {
func GetApiKeysHandler() restapi.ControllerHandler {
return func(w http.ResponseWriter, r *http.Request) {
ctx := GetBaseContext(r)
dbService, err := GetDatabaseService(ctx)
dbService, err := serviceprovider.GetDatabaseService(ctx)
if err != nil {
ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError))
return
Expand Down Expand Up @@ -105,7 +107,7 @@ func GetApiKeysHandler() restapi.ControllerHandler {
func DeleteApiKeyHandler() restapi.ControllerHandler {
return func(w http.ResponseWriter, r *http.Request) {
ctx := GetBaseContext(r)
dbService, err := GetDatabaseService(ctx)
dbService, err := serviceprovider.GetDatabaseService(ctx)
if err != nil {
ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError))
return
Expand Down Expand Up @@ -141,7 +143,7 @@ func DeleteApiKeyHandler() restapi.ControllerHandler {
func GetApiKeyHandler() restapi.ControllerHandler {
return func(w http.ResponseWriter, r *http.Request) {
ctx := GetBaseContext(r)
dbService, err := GetDatabaseService(ctx)
dbService, err := serviceprovider.GetDatabaseService(ctx)
if err != nil {
ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError))
return
Expand Down Expand Up @@ -190,7 +192,7 @@ func CreateApiKeyHandler() restapi.ControllerHandler {
return
}

dbService, err := GetDatabaseService(ctx)
dbService, err := serviceprovider.GetDatabaseService(ctx)
if err != nil {
ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError))
return
Expand All @@ -204,6 +206,7 @@ func CreateApiKeyHandler() restapi.ControllerHandler {
return
}
response := mappers.ApiKeyDtoToApiKeyResponse(*dtoApiKeyResult)
response.Encoded = base64.StdEncoding.EncodeToString([]byte(request.Key + ":" + request.Secret))
if err != nil {
ReturnApiError(ctx, w, models.NewFromError(err))
return
Expand All @@ -229,7 +232,7 @@ func CreateApiKeyHandler() restapi.ControllerHandler {
func RevokeApiKeyHandler() restapi.ControllerHandler {
return func(w http.ResponseWriter, r *http.Request) {
ctx := GetBaseContext(r)
dbService, err := GetDatabaseService(ctx)
dbService, err := serviceprovider.GetDatabaseService(ctx)
if err != nil {
ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError))
return
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func GetTokenHandler() restapi.ControllerHandler {
return
}

dbService, err := GetDatabaseService(ctx)
dbService, err := serviceprovider.GetDatabaseService(ctx)
if err != nil {
ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError))
return
Expand Down
Loading