VMware vSphere Integrated Containers provider (#206)

* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
This commit is contained in:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

View File

@@ -0,0 +1,298 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package restapi
import (
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
goruntime "runtime"
"github.com/Sirupsen/logrus"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/rs/cors"
"github.com/tylerb/graceful"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
)
// This file is safe to edit. Once it exists it will not be overwritten
//go:generate swagger generate server --target ../lib/apiservers/service --name --spec ../lib/apiservers/service/swagger.json --exclude-main
var loggingOption = struct {
Directory string `long:"log-directory" description:"the directory where vic-machine-server log is stored" default:"/var/log/vic-machine-server" env:"LOG_DIRECTORY"`
Level string `long:"log-level" description:"the minimum log level for vic-machine-server log messages" default:"debug" env:"LOG_LEVEL" choice:"debug" choice:"info" choice:"warning" choice:"error"`
}{}
// logger is a workaround used to pass the logger instance from configureAPI to setupGlobalMiddleware
var logger *logrus.Logger
func configureFlags(api *operations.VicMachineAPI) {
// api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... }
api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{
{
ShortDescription: "Logging options",
LongDescription: "Specify a directory for storing vic-machine service log",
Options: &loggingOption,
},
}
}
func configureAPI(api *operations.VicMachineAPI) http.Handler {
// configure the api here
api.ServeError = errors.ServeError
// configure logging to user specified directory
logger = configureLogger()
api.Logger = logger.Infof
api.Logger("Starting Service. Version: %q", version.GetBuild().ShortVersion())
api.JSONConsumer = runtime.JSONConsumer()
api.JSONProducer = runtime.JSONProducer()
api.TxtProducer = runtime.TextProducer()
// Applies when the Authorization header is set with the Basic scheme
api.BasicAuth = handlers.BasicAuth
api.SessionAuth = handlers.SessionAuth
// GET /container
api.GetHandler = operations.GetHandlerFunc(func(params operations.GetParams) middleware.Responder {
return middleware.NotImplemented("operation .Get has not yet been implemented")
})
// GET /container/hello
api.GetHelloHandler = &handlers.HelloGet{}
// GET /container/version
api.GetVersionHandler = &handlers.VersionGet{}
// POST /container/target/{target}
api.PostTargetTargetHandler = operations.PostTargetTargetHandlerFunc(func(params operations.PostTargetTargetParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PostTargetTarget has not yet been implemented")
})
// GET /container/target/{target}/vch
api.GetTargetTargetVchHandler = &handlers.VCHListGet{}
// POST /container/target/{target}/vch
api.PostTargetTargetVchHandler = &handlers.VCHCreate{}
// GET /container/target/{target}/vch/{vch-id}
api.GetTargetTargetVchVchIDHandler = &handlers.VCHGet{}
// GET /container/target/{target}/vch/{vch-id}/certificate
api.GetTargetTargetVchVchIDCertificateHandler = &handlers.VCHCertGet{}
// GET /container/target/{target}/vch/{vch-id}/log
api.GetTargetTargetVchVchIDLogHandler = &handlers.VCHLogGet{}
// GET /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}/certificate
api.GetTargetTargetDatacenterDatacenterVchVchIDCertificateHandler = &handlers.VCHDatacenterCertGet{}
// GET /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}/log
api.GetTargetTargetDatacenterDatacenterVchVchIDLogHandler = &handlers.VCHDatacenterLogGet{}
// PUT /container/target/{target}/vch/{vch-id}
api.PutTargetTargetVchVchIDHandler = operations.PutTargetTargetVchVchIDHandlerFunc(func(params operations.PutTargetTargetVchVchIDParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PutTargetTargetVchVchID has not yet been implemented")
})
// PATCH /container/target/{target}/vch/{vch-id}
api.PatchTargetTargetVchVchIDHandler = operations.PatchTargetTargetVchVchIDHandlerFunc(func(params operations.PatchTargetTargetVchVchIDParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PatchTargetTargetVchVchID has not yet been implemented")
})
// POST /container/target/{target}/vch/{vch-id}
api.PostTargetTargetVchVchIDHandler = operations.PostTargetTargetVchVchIDHandlerFunc(func(params operations.PostTargetTargetVchVchIDParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PostTargetTargetVchVchID has not yet been implemented")
})
// DELETE /container/target/{target}/vch/{vch-id}
api.DeleteTargetTargetVchVchIDHandler = &handlers.VCHDelete{}
// POST /container/target/{target}/datacenter/{datacenter}
api.PostTargetTargetDatacenterDatacenterHandler = operations.PostTargetTargetDatacenterDatacenterHandlerFunc(func(params operations.PostTargetTargetDatacenterDatacenterParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PostTargetTargetDatacenterDatacenter has not yet been implemented")
})
// GET /container/target/{target}/datacenter/{datacenter}/vch
api.GetTargetTargetDatacenterDatacenterVchHandler = &handlers.VCHDatacenterListGet{}
// POST /container/target/{target}/datacenter/{datacenter}/vch
api.PostTargetTargetDatacenterDatacenterVchHandler = &handlers.VCHDatacenterCreate{}
// GET /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}
api.GetTargetTargetDatacenterDatacenterVchVchIDHandler = &handlers.VCHDatacenterGet{}
// PUT /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}
api.PutTargetTargetDatacenterDatacenterVchVchIDHandler = operations.PutTargetTargetDatacenterDatacenterVchVchIDHandlerFunc(func(params operations.PutTargetTargetDatacenterDatacenterVchVchIDParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PutTargetTargetDatacenterDatacenterVchVchID has not yet been implemented")
})
// PATCH /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}
api.PatchTargetTargetDatacenterDatacenterVchVchIDHandler = operations.PatchTargetTargetDatacenterDatacenterVchVchIDHandlerFunc(func(params operations.PatchTargetTargetDatacenterDatacenterVchVchIDParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PatchTargetTargetDatacenterDatacenterVchVchID has not yet been implemented")
})
// POST /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}
api.PostTargetTargetDatacenterDatacenterVchVchIDHandler = operations.PostTargetTargetDatacenterDatacenterVchVchIDHandlerFunc(func(params operations.PostTargetTargetDatacenterDatacenterVchVchIDParams, principal interface{}) middleware.Responder {
return middleware.NotImplemented("operation .PostTargetTargetDatacenterDatacenterVchVchID has not yet been implemented")
})
// DELETE /container/target/{target}/datacenter/{datacenter}/vch/{vch-id}
api.DeleteTargetTargetDatacenterDatacenterVchVchIDHandler = &handlers.VCHDatacenterDelete{}
api.ServerShutdown = func() {}
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
// Make all necessary changes to the TLS configuration here.
}
// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *graceful.Server, scheme string) {
}
// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
func setupMiddlewares(handler http.Handler) http.Handler {
return handler
}
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
// These settings have security implications. These settings should not be changed without appropriate review.
// For more information, see the relevant section of the design document:
// https://github.com/vmware/vic/blob/7f575392df99642c5edd8f539a74fe9c89155b00/doc/design/vic-machine/service.md#cross-origin-requests--cross-site-request-forgery
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedHeaders: []string{"Authorization", "Content-Type", "User-Agent", "X-VMWARE-TICKET"},
AllowedMethods: []string{"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"},
ExposedHeaders: []string{"Content-Length"},
AllowCredentials: false,
})
return addLogging(addPanicRecovery(c.Handler(handler)))
}
func addLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
op := trace.NewOperation(r.Context(), "%s %s request to %s", r.Proto, r.Method, r.URL.Path)
lr := r.WithContext(op)
lw := NewLoggingResponseWriter(w)
op.Infof("Request: %s %s %s", r.Method, r.URL.Path, r.Proto)
status := "??? UNKNOWN"
defer func() {
op.Infof("Response: %s", status)
}()
next.ServeHTTP(lw, lr)
status = fmt.Sprintf("%d %s", lw.status, http.StatusText(lw.status))
})
}
func configureLogger() *logrus.Logger {
l := trace.Logger
if _, err := os.Stat(loggingOption.Directory); os.IsNotExist(err) {
os.MkdirAll(loggingOption.Directory, 0700)
}
path := loggingOption.Directory + "/vic-machine-server.log"
file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
log.Fatalf("Failed to open log file %s: %s", path, err)
}
l.Out = file
level, err := logrus.ParseLevel(loggingOption.Level)
if err != nil {
log.Printf("Error parsing log level %s: %s", loggingOption.Level, err)
level = logrus.DebugLevel
}
l.Level = level
// In case code uses the global logrus logger (it shouldn't):
logrus.SetOutput(file)
return l
}
// addPanicRecovery middleware logs the panic err message and stack trace, and returns a json http response
func addPanicRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
op := trace.FromContext(r.Context(), "Panic Recovery")
buf := make([]byte, 4096)
bytes := goruntime.Stack(buf, false)
stack := string(buf[:bytes])
op.Errorf("PANIC: %s\n%s", err, stack)
w.WriteHeader(http.StatusInternalServerError)
e := models.Error{Message: fmt.Sprint(err)}
json.NewEncoder(w).Encode(e)
}
}()
next.ServeHTTP(w, r)
})
}
// Reference for LoggingResponseWriter struct:
// http://ndersson.me/post/capturing_status_code_in_net_http/
type LoggingResponseWriter struct {
http.ResponseWriter
status int
}
func NewLoggingResponseWriter(w http.ResponseWriter) *LoggingResponseWriter {
return &LoggingResponseWriter{
ResponseWriter: w,
}
}
func (w *LoggingResponseWriter) WriteHeader(code int) {
w.status = code
w.ResponseWriter.WriteHeader(code)
}

View File

@@ -0,0 +1,32 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
type Credentials struct {
user string
pass string
}
type Session struct {
ticket string
}
func BasicAuth(user string, pass string) (interface{}, error) {
return Credentials{user: user, pass: pass}, nil
}
func SessionAuth(ticket string) (interface{}, error) {
return Session{ticket: ticket}, nil
}

View File

@@ -0,0 +1,203 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"fmt"
"net/http"
"net/url"
"github.com/docker/docker/opts"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/cmd/vic-machine/common"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/management"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
"github.com/vmware/vic/pkg/vsphere/vm"
)
type buildDataParams struct {
target string
thumbprint *string
datacenter *string
computeResource *string
vchID *string
}
func buildDataAndValidateTarget(op trace.Operation, params buildDataParams, principal interface{}) (*data.Data, *validate.Validator, error) {
data := &data.Data{
Target: &common.Target{
URL: &url.URL{Host: params.target},
},
}
if c, ok := principal.(Credentials); ok {
data.Target.User = c.user
data.Target.Password = &c.pass
} else {
data.Target.CloneTicket = principal.(Session).ticket
}
if err := data.HasCredentials(op); err != nil {
return data, nil, util.NewError(http.StatusUnauthorized, "Invalid Credentials: %s", err)
}
if params.thumbprint != nil {
data.Thumbprint = *params.thumbprint
}
if params.computeResource != nil {
data.ComputeResourcePath = *params.computeResource
}
if params.vchID != nil {
data.ID = *params.vchID
}
// TODO (#6032): clean this up
var validator *validate.Validator
if params.datacenter != nil {
v, err := validate.NewValidator(op, data)
if err != nil {
return data, nil, util.NewError(http.StatusBadRequest, "Validation Error: %s", err)
}
datacenterManagedObjectReference := types.ManagedObjectReference{Type: "Datacenter", Value: *params.datacenter}
datacenterObject, err := v.Session.Finder.ObjectReference(op, datacenterManagedObjectReference)
if err != nil {
return nil, nil, util.WrapError(http.StatusNotFound, err)
}
dc, ok := datacenterObject.(*object.Datacenter)
if !ok {
return data, nil, util.NewError(http.StatusBadRequest, "Validation Error: datacenter parameter is not a datacenter moref")
}
// Set validator datacenter path and correspondent validator session config
v.DatacenterPath = dc.Name()
v.Session.DatacenterPath = v.DatacenterPath
v.Session.Datacenter = dc
v.Session.Finder.SetDatacenter(dc)
// Do what validator.session.Populate would have done if datacenterPath is set
if v.Session.Datacenter != nil {
folders, err := v.Session.Datacenter.Folders(op)
if err != nil {
return data, nil, util.NewError(http.StatusBadRequest, "Validation Error: error finding datacenter folders: %s", err)
}
v.Session.VMFolder = folders.VmFolder
}
validator = v
} else {
v, err := validate.NewValidator(op, data)
if err != nil {
return data, nil, util.NewError(http.StatusBadRequest, "Validation Error: %s", err)
}
// If dc is not set, and multiple datacenters are available, operate on all datacenters.
v.AllowEmptyDC()
validator = v
}
if _, err := validator.ValidateTarget(op, data); err != nil {
return data, nil, util.NewError(http.StatusBadRequest, "Target validation failed: %s", err)
}
if _, err := validator.ValidateCompute(op, data, false); err != nil {
return data, nil, util.NewError(http.StatusBadRequest, "Compute resource validation failed: %s", err)
}
return data, validator, nil
}
// Copied from list.go, and appears to be present other places. TODO (#6032): deduplicate
func upgradeStatusMessage(op trace.Operation, vch *vm.VirtualMachine, installerVer *version.Build, vchVer *version.Build) string {
if sameVer := installerVer.Equal(vchVer); sameVer {
return "Up to date"
}
upgrading, err := vch.VCHUpdateStatus(op)
if err != nil {
return fmt.Sprintf("Unknown: %s", err)
}
if upgrading {
return "Upgrade in progress"
}
canUpgrade, err := installerVer.IsNewer(vchVer)
if err != nil {
return fmt.Sprintf("Unknown: %s", err)
}
if canUpgrade {
return fmt.Sprintf("Upgradeable to %s", installerVer.ShortVersion())
}
oldInstaller, err := installerVer.IsOlder(vchVer)
if err != nil {
return fmt.Sprintf("Unknown: %s", err)
}
if oldInstaller {
return fmt.Sprintf("VCH has newer version")
}
// can't get here
return "Invalid upgrade status"
}
func getVCHConfig(op trace.Operation, d *data.Data, validator *validate.Validator) (*config.VirtualContainerHostConfigSpec, error) {
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
vch, err := executor.NewVCHFromID(d.ID)
if err != nil {
return nil, util.NewError(http.StatusNotFound, fmt.Sprintf("Unable to find VCH %s: %s", d.ID, err))
}
err = validate.SetDataFromVM(validator.Context, validator.Session.Finder, vch, d)
if err != nil {
return nil, util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to load VCH data: %s", err))
}
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
if err != nil {
return nil, fmt.Errorf("Unable to retrieve VCH information: %s", err)
}
return vchConfig, nil
}
func getAddresses(vchConfig *config.VirtualContainerHostConfigSpec) (dockerHost, adminPortal string) {
if client := vchConfig.ExecutorConfig.Networks["client"]; client != nil {
if publicIP := client.Assigned.IP; publicIP != nil {
var dockerPort = opts.DefaultTLSHTTPPort
if vchConfig.HostCertificate.IsNil() {
dockerPort = opts.DefaultHTTPPort
}
dockerHost = fmt.Sprintf("%s:%d", publicIP, dockerPort)
adminPortal = fmt.Sprintf("https://%s:%d", publicIP, constants.VchAdminPortalPort)
}
}
return
}

View File

@@ -0,0 +1,33 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"github.com/go-openapi/runtime/middleware"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
)
const (
welcomeMessage = "You have successfully accessed the VCH Management API."
)
// HelloGet is the handler for accessing the version information for the service
type HelloGet struct {
}
func (h *HelloGet) Handle(params operations.GetHelloParams) middleware.Responder {
return operations.NewGetVersionOK().WithPayload(welcomeMessage)
}

View File

@@ -0,0 +1,72 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"net/http"
)
func StatusCode(err error) int {
e, ok := err.(statusCode)
if !ok {
return http.StatusInternalServerError
}
return e.Code()
}
func NewError(code int, message string, a ...interface{}) error {
if a != nil {
return httpError{code: code, message: fmt.Sprintf(message, a...)}
}
return httpError{code: code, message: message}
}
func WrapError(code int, err error) error {
return wrappedError{error: err, code: code}
}
// Pattern based on https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
type statusCode interface {
Code() int
}
type httpError struct {
code int
message string
}
func (e httpError) Code() int {
return e.code
}
func (e httpError) Error() string {
return e.message
}
type wrappedError struct {
error
code int
}
func (e wrappedError) Code() int {
return e.code
}
func (e wrappedError) Error() string {
return e.error.Error()
}

View File

@@ -0,0 +1,78 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"net/http"
"testing"
)
func TestNewError(t *testing.T) {
e := NewError(123, "new error %d %s %q")
c := StatusCode(e)
if c != 123 {
t.Errorf("Status code was %d, not 123.", c)
}
if e.Error() != "new error %d %s %q" {
t.Error("NewError did not preserve message.")
}
}
func TestNewErrorWithArgs(t *testing.T) {
e := NewError(123, "new error %d %s %q", 1, "a", "foo")
c := StatusCode(e)
if c != 123 {
t.Errorf("Status code was %d, not 123.", c)
}
if e.Error() != "new error 1 a \"foo\"" {
t.Error("NewError did not preserve message.")
}
}
func TestWrappedError(t *testing.T) {
e := WrapError(234, fmt.Errorf("fmt error"))
c := StatusCode(e)
if c != 234 {
t.Errorf("Status code was %d, not 234.", c)
}
if e.Error() != "fmt error" {
t.Error("WrapError did not preserve message.")
}
}
func TestDoublyWrappedError(t *testing.T) {
e := WrapError(234, WrapError(123, fmt.Errorf("fmt error")))
c := StatusCode(e)
if c != 234 {
t.Errorf("Status code was %d, not 234.", c)
}
}
func TestStatusCodeFallback(t *testing.T) {
e := fmt.Errorf("fmt error")
c := StatusCode(e)
if c != http.StatusInternalServerError {
t.Errorf("Default status code was %d, not 500.", c)
}
}

View File

@@ -0,0 +1,126 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"fmt"
"net/http"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/trace"
)
type VCHCertGet struct{}
type VCHDatacenterCertGet struct{}
func (h *VCHCertGet) Handle(params operations.GetTargetTargetVchVchIDCertificateParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHCertGet: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetVchVchIDCertificateDefault(
util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
c, err := getVCHCert(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetVchVchIDCertificateDefault(
util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
cert := asPemCertificate(c.Cert)
return NewGetTargetTargetVchVchIDCertificateOK(cert.Pem)
}
func (h *VCHDatacenterCertGet) Handle(params operations.GetTargetTargetDatacenterDatacenterVchVchIDCertificateParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDatacenterCertGet: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
datacenter: &params.Datacenter,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDCertificateDefault(
util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
c, err := getVCHCert(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDCertificateDefault(
util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
cert := asPemCertificate(c.Cert)
return NewGetTargetTargetDatacenterDatacenterVchVchIDCertificateOK(cert.Pem)
}
func getVCHCert(op trace.Operation, d *data.Data, validator *validate.Validator) (*config.RawCertificate, error) {
vchConfig, err := getVCHConfig(op, d, validator)
if err != nil {
return nil, err
}
if vchConfig.HostCertificate.IsNil() {
return nil, util.NewError(http.StatusNotFound, fmt.Sprintf("No certificate found for VCH %s", d.ID))
}
return vchConfig.HostCertificate, nil
}
// GetTargetTargetVchVchIDCertificateOK and the methods below are actually borrowed directly from generated swagger code.
// They are moved into this file and altered to use the TextProducer when returning a PEM certificate, as swagger does not
// directly support application/x-pem-file on the server side.
type GetTargetTargetVchVchIDCertificateOK struct {
*operations.GetTargetTargetVchVchIDCertificateOK
}
func NewGetTargetTargetVchVchIDCertificateOK(payload models.PEM) *GetTargetTargetVchVchIDCertificateOK {
return &GetTargetTargetVchVchIDCertificateOK{operations.NewGetTargetTargetVchVchIDCertificateOK().WithPayload(payload)}
}
func (o *GetTargetTargetVchVchIDCertificateOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
o.GetTargetTargetVchVchIDCertificateOK.WriteResponse(rw, runtime.TextProducer())
}
type GetTargetTargetDatacenterDatacenterVchVchIDCertificateOK struct {
*operations.GetTargetTargetDatacenterDatacenterVchVchIDCertificateOK
}
func NewGetTargetTargetDatacenterDatacenterVchVchIDCertificateOK(payload models.PEM) *GetTargetTargetDatacenterDatacenterVchVchIDCertificateOK {
return &GetTargetTargetDatacenterDatacenterVchVchIDCertificateOK{operations.NewGetTargetTargetDatacenterDatacenterVchVchIDCertificateOK().WithPayload(payload)}
}
func (o *GetTargetTargetDatacenterDatacenterVchVchIDCertificateOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
o.GetTargetTargetDatacenterDatacenterVchVchIDCertificateOK.WriteResponse(rw, runtime.TextProducer())
}

View File

@@ -0,0 +1,671 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"context"
"fmt"
"math"
"net"
"net/http"
"path"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/go-units"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/govmomi/list"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/cmd/vic-machine/common"
"github.com/vmware/vic/cmd/vic-machine/create"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/management"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/lib/install/vchlog"
"github.com/vmware/vic/pkg/ip"
viclog "github.com/vmware/vic/pkg/log"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
)
const (
logFile = "vic-machine.log" // name of local log file
)
// This interface is declared so that we can enable mocking finder in tests
// as the govmomi types do not use interfaces themselves.
type finder interface {
Element(context.Context, types.ManagedObjectReference) (*list.Element, error)
}
// VCHCreate is the handler for creating a VCH
type VCHCreate struct {
}
// VCHDatacenterCreate is the handler for creating a VCH within a Datacenter
type VCHDatacenterCreate struct {
}
// Handle is the handler implementation for VCH creation without a datacenter
func (h *VCHCreate) Handle(params operations.PostTargetTargetVchParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHCreate")
datastoreLogger := setUpLogger(&op)
defer datastoreLogger.Close()
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewPostTargetTargetVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
c, err := buildCreate(op, d, finder(validator.Session.Finder), params.Vch)
if err != nil {
return operations.NewPostTargetTargetVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
task, err := handleCreate(op, c, validator, datastoreLogger)
if err != nil {
return operations.NewPostTargetTargetVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewPostTargetTargetVchCreated().WithPayload(operations.PostTargetTargetVchCreatedBody{Task: task})
}
// Handle is the handler implementation for VCH creation with a datacenter
func (h *VCHDatacenterCreate) Handle(params operations.PostTargetTargetDatacenterDatacenterVchParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDatacenterCreate")
datastoreLogger := setUpLogger(&op)
defer datastoreLogger.Close()
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
datacenter: &params.Datacenter,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewPostTargetTargetDatacenterDatacenterVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
c, err := buildCreate(op, d, validator.Session.Finder, params.Vch)
if err != nil {
return operations.NewPostTargetTargetDatacenterDatacenterVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
task, err := handleCreate(op, c, validator, datastoreLogger)
if err != nil {
return operations.NewPostTargetTargetDatacenterDatacenterVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewPostTargetTargetDatacenterDatacenterVchCreated().WithPayload(operations.PostTargetTargetDatacenterDatacenterVchCreatedBody{Task: task})
}
func setUpLogger(op *trace.Operation) *vchlog.VCHLogger {
log := vchlog.New()
op.Logger = logrus.New()
op.Logger.Out = log.GetPipe()
op.Logger.Level = logrus.DebugLevel
op.Logger.Formatter = viclog.NewTextFormatter()
op.Logger.Infof("Starting API-based VCH Creation. Version: %q", version.GetBuild().ShortVersion())
go log.Run()
return log
}
func buildCreate(op trace.Operation, d *data.Data, finder finder, vch *models.VCH) (*create.Create, error) {
c := &create.Create{Data: d}
// TODO (#6032): deduplicate with create.processParams
if vch != nil {
if vch.Version != "" && version.String() != string(vch.Version) {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid version: %s", vch.Version))
}
c.DisplayName = vch.Name
// TODO (#6710): move validation to swagger
if err := common.CheckUnsupportedChars(c.DisplayName); err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid display name: %s", err))
}
if len(c.DisplayName) > create.MaxDisplayNameLen {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid display name: length exceeds %d characters", create.MaxDisplayNameLen))
}
debug := int(vch.Debug)
c.Debug.Debug = &debug
if vch.Compute != nil {
if vch.Compute.CPU != nil {
c.ResourceLimits.VCHCPULimitsMHz = mhzFromValueHertz(vch.Compute.CPU.Limit)
c.ResourceLimits.VCHCPUReservationsMHz = mhzFromValueHertz(vch.Compute.CPU.Reservation)
c.ResourceLimits.VCHCPUShares = fromShares(vch.Compute.CPU.Shares)
}
if vch.Compute.Memory != nil {
c.ResourceLimits.VCHMemoryLimitsMB = mbFromValueBytes(vch.Compute.Memory.Limit)
c.ResourceLimits.VCHMemoryReservationsMB = mbFromValueBytes(vch.Compute.Memory.Reservation)
c.ResourceLimits.VCHMemoryShares = fromShares(vch.Compute.Memory.Shares)
}
resourcePath, err := fromManagedObject(op, finder, "ResourcePool", vch.Compute.Resource) // TODO (#6711): Do we need to handle clusters differently?
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error finding resource pool: %s", err))
}
if resourcePath == "" {
return nil, util.NewError(http.StatusBadRequest, "Resource pool must be specified (by name or id)")
}
c.ComputeResourcePath = resourcePath
}
if vch.Network != nil {
if vch.Network.Bridge != nil {
path, err := fromManagedObject(op, finder, "Network", vch.Network.Bridge.PortGroup)
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error finding bridge network: %s", err))
}
if path == "" {
return nil, util.NewError(http.StatusBadRequest, "Bridge network portgroup must be specified (by name or id)")
}
c.BridgeNetworkName = path
c.BridgeIPRange = fromCIDR(&vch.Network.Bridge.IPRange)
if err := c.ProcessBridgeNetwork(); err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
}
if vch.Network.Client != nil {
path, err := fromManagedObject(op, finder, "Network", vch.Network.Client.PortGroup)
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error finding client network portgroup: %s", err))
}
if path == "" {
return nil, util.NewError(http.StatusBadRequest, "Client network portgroup must be specified (by name or id)")
}
c.ClientNetworkName = path
c.ClientNetworkGateway = fromGateway(vch.Network.Client.Gateway)
c.ClientNetworkIP = fromCIDR(&vch.Network.Client.Static)
if err := c.ProcessNetwork(op, &c.Data.ClientNetwork, "client", c.ClientNetworkName, c.ClientNetworkIP, c.ClientNetworkGateway); err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
}
if vch.Network.Management != nil {
path, err := fromManagedObject(op, finder, "Network", vch.Network.Management.PortGroup)
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error finding management network portgroup: %s", err))
}
if path == "" {
return nil, util.NewError(http.StatusBadRequest, "Management network portgroup must be specified (by name or id)")
}
c.ManagementNetworkName = path
c.ManagementNetworkGateway = fromGateway(vch.Network.Management.Gateway)
c.ManagementNetworkIP = fromCIDR(&vch.Network.Management.Static)
if err := c.ProcessNetwork(op, &c.Data.ManagementNetwork, "management", c.ManagementNetworkName, c.ManagementNetworkIP, c.ManagementNetworkGateway); err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
}
if vch.Network.Public != nil {
path, err := fromManagedObject(op, finder, "Network", vch.Network.Public.PortGroup)
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error finding public network portgroup: %s", err))
}
if path == "" {
return nil, util.NewError(http.StatusBadRequest, "Public network portgroup must be specified (by name or id)")
}
c.PublicNetworkName = path
c.PublicNetworkGateway = fromGateway(vch.Network.Public.Gateway)
c.PublicNetworkIP = fromCIDR(&vch.Network.Public.Static)
if err := c.ProcessNetwork(op, &c.Data.PublicNetwork, "public", c.PublicNetworkName, c.PublicNetworkIP, c.PublicNetworkGateway); err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
c.Nameservers = common.DNS{
DNS: fromIPAddresses(vch.Network.Public.Nameservers),
}
c.DNS, err = c.Nameservers.ProcessDNSServers(op)
if err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
}
if vch.Network.Container != nil {
containerNetworks := common.ContainerNetworks{
MappedNetworks: make(map[string]string),
MappedNetworksGateways: make(map[string]net.IPNet),
MappedNetworksIPRanges: make(map[string][]ip.Range),
MappedNetworksDNS: make(map[string][]net.IP),
MappedNetworksFirewalls: make(map[string]executor.TrustLevel),
}
for _, cnetwork := range vch.Network.Container {
alias := cnetwork.Alias
path, err := fromManagedObject(op, finder, "Network", cnetwork.PortGroup)
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error finding portgroup for container network %s: %s", alias, err))
}
if path == "" {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Container network %s portgroup must be specified (by name or id)", alias))
}
containerNetworks.MappedNetworks[alias] = path
if cnetwork.Gateway != nil {
address := net.ParseIP(string(cnetwork.Gateway.Address))
if address == nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error parsing gateway IP %s for container network %s", cnetwork.Gateway.Address, alias))
}
if cnetwork.Gateway.RoutingDestinations == nil || len(cnetwork.Gateway.RoutingDestinations) != 1 {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error parsing network mask for container network %s: exactly one subnet must be specified", alias))
}
_, mask, err := net.ParseCIDR(string(cnetwork.Gateway.RoutingDestinations[0]))
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error parsing network mask for container network %s: %s", alias, err))
}
containerNetworks.MappedNetworksGateways[alias] = net.IPNet{
IP: address,
Mask: mask.Mask,
}
}
ipranges := make([]ip.Range, 0, len(cnetwork.IPRanges))
for _, ipRange := range cnetwork.IPRanges {
r := ip.ParseRange(string(ipRange))
ipranges = append(ipranges, *r)
}
containerNetworks.MappedNetworksIPRanges[alias] = ipranges
nameservers := make([]net.IP, 0, len(cnetwork.Nameservers))
for _, nameserver := range cnetwork.Nameservers {
n := net.ParseIP(string(nameserver))
nameservers = append(nameservers, n)
}
containerNetworks.MappedNetworksDNS[alias] = nameservers
if cnetwork.Firewall != "" {
trustLevel, err := executor.ParseTrustLevel(cnetwork.Firewall)
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error parsing trust level for container network %s: %s", alias, err))
}
containerNetworks.MappedNetworksFirewalls[alias] = trustLevel
}
}
c.ContainerNetworks = containerNetworks
}
}
if vch.Storage != nil {
if vch.Storage.ImageStores != nil && len(vch.Storage.ImageStores) > 0 {
c.ImageDatastorePath = vch.Storage.ImageStores[0] // TODO (#6712): many vs. one mismatch
}
if err := common.CheckUnsupportedCharsDatastore(c.ImageDatastorePath); err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
if vch.Storage.VolumeStores != nil {
volumes := make([]string, 0, len(vch.Storage.VolumeStores))
for _, v := range vch.Storage.VolumeStores {
volumes = append(volumes, fmt.Sprintf("%s:%s", v.Datastore, v.Label))
}
vs := common.VolumeStores{VolumeStores: cli.StringSlice(volumes)}
volumeLocations, err := vs.ProcessVolumeStores()
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error processing volume stores: %s", err))
}
c.VolumeLocations = volumeLocations
}
c.ScratchSize = constants.DefaultBaseImageScratchSize
if vch.Storage.BaseImageSize != nil {
c.ScratchSize = fromValueBytesMetric(vch.Storage.BaseImageSize)
}
}
if vch.Auth != nil {
c.Certs.NoTLS = vch.Auth.NoTLS
if vch.Auth.Client != nil {
c.Certs.NoTLSverify = vch.Auth.Client.NoTLSVerify
c.Certs.ClientCAs = fromPemCertificates(vch.Auth.Client.CertificateAuthorities)
c.ClientCAs = c.Certs.ClientCAs
}
if vch.Auth.Server != nil {
if vch.Auth.Server.Generate != nil {
c.Certs.Cname = vch.Auth.Server.Generate.Cname
c.Certs.Org = vch.Auth.Server.Generate.Organization
c.Certs.KeySize = fromValueBits(vch.Auth.Server.Generate.Size)
c.Certs.NoSaveToDisk = true
c.Certs.Networks = c.Networks
if err := c.Certs.ProcessCertificates(op, c.DisplayName, c.Force, 0); err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error generating certificates: %s", err))
}
} else {
c.Certs.CertPEM = []byte(vch.Auth.Server.Certificate.Pem)
c.Certs.KeyPEM = []byte(vch.Auth.Server.PrivateKey.Pem)
}
c.KeyPEM = c.Certs.KeyPEM
c.CertPEM = c.Certs.CertPEM
c.ClientCAs = c.Certs.ClientCAs
}
}
c.MemoryMB = constants.DefaultEndpointMemoryMB
if vch.Endpoint != nil {
if vch.Endpoint.Memory != nil {
c.MemoryMB = *mbFromValueBytes(vch.Endpoint.Memory)
}
if vch.Endpoint.CPU != nil {
c.NumCPUs = int(vch.Endpoint.CPU.Sockets)
}
if vch.Endpoint.OperationsCredentials != nil {
opsPassword := string(vch.Endpoint.OperationsCredentials.Password)
c.OpsCredentials = common.OpsCredentials{
OpsUser: &vch.Endpoint.OperationsCredentials.User,
OpsPassword: &opsPassword,
GrantPerms: &vch.Endpoint.OperationsCredentials.GrantPermissions,
}
}
}
if err := c.OpsCredentials.ProcessOpsCredentials(op, true, c.Target.User, c.Target.Password); err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}
if vch.Registry != nil {
c.InsecureRegistries = vch.Registry.Insecure
c.WhitelistRegistries = vch.Registry.Whitelist
c.RegistryCAs = fromPemCertificates(vch.Registry.CertificateAuthorities)
if vch.Registry.ImageFetchProxy != nil {
c.Proxies = fromImageFetchProxy(vch.Registry.ImageFetchProxy)
hproxy, sproxy, err := c.Proxies.ProcessProxies()
if err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error processing proxies: %s", err))
}
c.HTTPProxy = hproxy
c.HTTPSProxy = sproxy
}
}
if vch.SyslogAddr != "" {
c.SyslogAddr = vch.SyslogAddr.String()
if err := c.ProcessSyslog(); err != nil {
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Error processing syslog server address: %s", err))
}
}
if vch.Container != nil && vch.Container.NameConvention != "" {
c.ContainerNameConvention = vch.Container.NameConvention
}
}
return c, nil
}
func handleCreate(op trace.Operation, c *create.Create, validator *validate.Validator, receiver vchlog.Receiver) (*strfmt.URI, error) {
vchConfig, err := validator.Validate(op, c.Data)
if err != nil {
issues := validator.GetIssues()
messages := make([]string, 0, len(issues))
for _, issue := range issues {
messages = append(messages, issue.Error())
}
return nil, util.NewError(http.StatusBadRequest, fmt.Sprintf("Failed to validate VCH: %s", strings.Join(messages, ", ")))
}
vConfig := validator.AddDeprecatedFields(op, vchConfig, c.Data)
// TODO (#6714): make this configurable
images := common.Images{}
vConfig.ImageFiles, err = images.CheckImagesFiles(op, true)
vConfig.ApplianceISO = path.Base(images.ApplianceISO)
vConfig.BootstrapISO = path.Base(images.BootstrapISO)
vConfig.HTTPProxy = c.HTTPProxy
vConfig.HTTPSProxy = c.HTTPSProxy
executor := management.NewDispatcher(op, validator.Session, nil, false)
err = executor.CreateVCH(vchConfig, vConfig, receiver)
if err != nil {
return nil, util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to create VCH: %s", err))
}
return nil, nil
}
func fromManagedObject(op trace.Operation, finder finder, t string, m *models.ManagedObject) (string, error) {
if m == nil {
return "", nil
}
if m.ID != "" {
managedObjectReference := types.ManagedObjectReference{Type: t, Value: m.ID}
element, err := finder.Element(op, managedObjectReference)
if err != nil {
return "", err
}
return element.Path, nil
}
return m.Name, nil
}
func fromCIDR(m *models.CIDR) string {
if m == nil {
return ""
}
return string(*m)
}
func fromCIDRs(m *[]models.CIDR) *[]string {
s := make([]string, 0, len(*m))
for _, d := range *m {
s = append(s, fromCIDR(&d))
}
return &s
}
func fromIPAddress(m *models.IPAddress) string {
if m == nil {
return ""
}
return string(*m)
}
func fromIPAddresses(m []models.IPAddress) []string {
s := make([]string, 0, len(m))
for _, ip := range m {
s = append(s, fromIPAddress(&ip))
}
return s
}
func fromGateway(m *models.Gateway) string {
if m == nil {
return ""
}
if m.RoutingDestinations == nil {
return fmt.Sprintf("%s",
m.Address,
)
}
return fmt.Sprintf("%s:%s",
strings.Join(*fromCIDRs(&m.RoutingDestinations), ","),
m.Address,
)
}
func fromValueBytesMetric(m *models.ValueBytesMetric) string {
v := float64(m.Value.Value)
var bytes float64
switch m.Value.Units {
case models.ValueBytesMetricUnitsB:
bytes = v
case models.ValueBytesMetricUnitsKB:
bytes = v * float64(units.KB)
case models.ValueBytesMetricUnitsMB:
bytes = v * float64(units.MB)
case models.ValueBytesMetricUnitsGB:
bytes = v * float64(units.GB)
case models.ValueBytesMetricUnitsTB:
bytes = v * float64(units.TB)
case models.ValueBytesMetricUnitsPB:
bytes = v * float64(units.PB)
}
return fmt.Sprintf("%d B", int64(bytes))
}
func mbFromValueBytes(m *models.ValueBytes) *int {
if m == nil {
return nil
}
v := float64(m.Value.Value)
var mbs float64
switch m.Value.Units {
case models.ValueBytesUnitsB:
mbs = v / float64(units.MiB)
case models.ValueBytesUnitsKiB:
mbs = v / (float64(units.MiB) / float64(units.KiB))
case models.ValueBytesUnitsMiB:
mbs = v
case models.ValueBytesUnitsGiB:
mbs = v * (float64(units.GiB) / float64(units.MiB))
case models.ValueBytesUnitsTiB:
mbs = v * (float64(units.TiB) / float64(units.MiB))
case models.ValueBytesUnitsPiB:
mbs = v * (float64(units.PiB) / float64(units.MiB))
}
i := int(math.Ceil(mbs))
return &i
}
func mhzFromValueHertz(m *models.ValueHertz) *int {
if m == nil {
return nil
}
v := float64(m.Value.Value)
var mhzs float64
switch m.Value.Units {
case models.ValueHertzUnitsHz:
mhzs = v / float64(units.MB)
case models.ValueHertzUnitsKHz:
mhzs = v / (float64(units.MB) / float64(units.KB))
case models.ValueHertzUnitsMHz:
mhzs = v
case models.ValueHertzUnitsGHz:
mhzs = v * (float64(units.GB) / float64(units.MB))
}
i := int(math.Ceil(mhzs))
return &i
}
func fromShares(m *models.Shares) *types.SharesInfo {
if m == nil {
return nil
}
var level types.SharesLevel
switch types.SharesLevel(m.Level) {
case types.SharesLevelLow:
level = types.SharesLevelLow
case types.SharesLevelNormal:
level = types.SharesLevelNormal
case types.SharesLevelHigh:
level = types.SharesLevelHigh
default:
level = types.SharesLevelCustom
}
return &types.SharesInfo{
Level: level,
Shares: int32(m.Number),
}
}
func fromValueBits(m *models.ValueBits) int {
return int(m.Value.Value)
}
func fromPemCertificates(m []*models.X509Data) []byte {
var b []byte
for _, ca := range m {
c := []byte(ca.Pem)
b = append(b, c...)
}
return b
}
func fromImageFetchProxy(p *models.VCHRegistryImageFetchProxy) common.Proxies {
http := string(p.HTTP)
https := string(p.HTTPS)
return common.Proxies{
HTTPProxy: &http,
HTTPSProxy: &https,
}
}

View File

@@ -0,0 +1,300 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"context"
"fmt"
"net/url"
"os"
"reflect"
"sort"
"testing"
"github.com/stretchr/testify/assert"
cli "gopkg.in/urfave/cli.v1"
"github.com/vmware/govmomi/list"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/cmd/vic-machine/common"
"github.com/vmware/vic/cmd/vic-machine/create"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/pkg/trace"
)
type mockFinder struct {
path string
}
func (mf mockFinder) Element(ctx context.Context, t types.ManagedObjectReference) (*list.Element, error) {
return &list.Element{
Path: t.Value,
}, nil
}
func TestFromManagedObject(t *testing.T) {
op := trace.NewOperation(context.Background(), "TestFromManagedObject")
var m *models.ManagedObject
expected := ""
actual, err := fromManagedObject(op, nil, "t", m)
assert.NoError(t, err, "Expected nil error, got %#v", err)
assert.Equal(t, expected, actual)
m = &models.ManagedObject{
Name: "testManagedObject",
}
mf := mockFinder{}
expected = m.Name
actual, err = fromManagedObject(op, mf, "t", m)
assert.NoError(t, err, "Expected nil error, got %#v", err)
assert.Equal(t, expected, actual)
m.ID = "testID"
expected = m.ID
actual, err = fromManagedObject(op, mf, "t", m)
assert.NoError(t, err, "Expected nil error, got %#v", err)
assert.Equal(t, expected, actual)
m.Name = ""
expected = m.ID
actual, err = fromManagedObject(op, mf, "t", m)
assert.NoError(t, err, "Expected nil error, got %#v", err)
assert.Equal(t, expected, actual)
}
func TestFromCIDR(t *testing.T) {
var m models.CIDR
expected := ""
actual := fromCIDR(&m)
assert.Equal(t, expected, actual)
m = "10.10.1.0/32"
expected = string(m)
actual = fromCIDR(&m)
assert.Equal(t, expected, actual)
}
func TestFromGateway(t *testing.T) {
var m *models.Gateway
expected := ""
actual := fromGateway(m)
assert.Equal(t, expected, actual)
m = &models.Gateway{
Address: "192.168.31.37",
RoutingDestinations: []models.CIDR{
"192.168.1.1/24",
"172.17.0.1/24",
},
}
expected = "192.168.1.1/24,172.17.0.1/24:192.168.31.37"
actual = fromGateway(m)
assert.Equal(t, expected, actual)
}
func TestCreateVCH(t *testing.T) {
vch := &models.VCH{
Name: "test-vch",
Debug: 3,
Compute: &models.VCHCompute{
Resource: &models.ManagedObject{
Name: "TestCluster",
},
},
Storage: &models.VCHStorage{
ImageStores: []string{
"ds://test/datastore",
},
},
Network: &models.VCHNetwork{
Bridge: &models.VCHNetworkBridge{
IPRange: "17.16.0.0/12",
PortGroup: &models.ManagedObject{
ID: "bridge", // required for mocked finder to work
Name: "bridge",
},
},
Public: &models.Network{
PortGroup: &models.ManagedObject{
ID: "public", // required for mock finder to work
Name: "public",
},
},
},
Registry: &models.VCHRegistry{
ImageFetchProxy: &models.VCHRegistryImageFetchProxy{
HTTP: "http://example.com",
HTTPS: "https://example.com",
},
Insecure: []string{
"https://insecure.example.com",
},
Whitelist: []string{
"10.0.0.0/8",
},
},
Auth: &models.VCHAuth{
Server: &models.VCHAuthServer{
Generate: &models.VCHAuthServerGenerate{
Cname: "vch.example.com",
Organization: []string{
"VMware, Inc.",
},
Size: &models.ValueBits{
Value: models.Value{Value: 2048},
Units: "bits",
},
},
},
},
SyslogAddr: "tcp://syslog.example.com:4444",
Container: &models.VCHContainer{
NameConvention: "container-{id}",
},
}
op := trace.NewOperation(context.Background(), "testing")
defer func() {
err := os.RemoveAll("test-vch")
assert.NoError(t, err, "Error removing temp directory: %s", err)
}()
pass := "testpass"
data := &data.Data{
Target: &common.Target{
URL: &url.URL{Host: "10.10.1.2"},
User: "testuser",
Password: &pass,
},
}
ca := newCreate()
ca.Data = data
ca.DisplayName = "test-vch"
err := ca.ProcessParams(op)
assert.NoError(t, err, "Error while processing params: %s", err)
op.Infof("ca EnvFile: %s", ca.Certs.EnvFile)
mf := mockFinder{}
cb, err := buildCreate(op, data, mf, vch)
assert.NoError(t, err, "Error while processing params: %s", err)
a := reflect.ValueOf(ca).Elem()
b := reflect.ValueOf(cb).Elem()
if err = compare(a, b, 0); err != nil {
t.Fatalf("Error comparing create structs: %s", err)
}
}
func newCreate() *create.Create {
debug := 3
ca := create.NewCreate()
ca.Debug = common.Debug{Debug: &debug}
ca.Compute = common.Compute{DisplayName: "TestCluster"}
ca.ImageDatastorePath = "ds://test/datastore"
ca.BridgeIPRange = "17.16.0.0/12"
ca.BridgeNetworkName = "bridge"
ca.PublicNetworkName = "public"
ca.Certs.Cname = "vch.example.com"
ca.Certs.Org = cli.StringSlice{"VMware, Inc."}
ca.Certs.KeySize = 2048
httpProxy := "http://example.com"
httpsProxy := "https://example.com"
ca.Proxies = common.Proxies{
HTTPProxy: &httpProxy,
HTTPSProxy: &httpsProxy,
}
ca.Registries = common.Registries{
InsecureRegistriesArg: cli.StringSlice{"https://insecure.example.com"},
WhitelistRegistriesArg: cli.StringSlice{"10.0.0.0/8"},
}
ca.SyslogAddr = "tcp://syslog.example.com:4444"
ca.ContainerNameConvention = "container-{id}"
ca.Certs.CertPath = "test-vch"
ca.Certs.NoSaveToDisk = true
return ca
}
func compare(a, b reflect.Value, index int) (err error) {
switch a.Kind() {
case reflect.Invalid, reflect.Uint8: // skip uint8 as generated cert data is not expected to match
// NOP
case reflect.Ptr:
ae := a.Elem()
be := b.Elem()
if !ae.IsValid() != !be.IsValid() {
return fmt.Errorf("Expected pointer validity to match for for %s", a.Type().Name())
}
return compare(ae, be, index)
case reflect.Interface:
return compare(a.Elem(), b.Elem(), index)
case reflect.Struct:
for i := 0; i < a.NumField(); i++ {
if err = compare(a.Field(i), b.Field(i), i); err != nil {
fmt.Printf("Field name a: %s, b: %s, index: %d\n", a.Type().Field(i).Name, b.Type().Field(i).Name, i)
return err
}
}
case reflect.Slice:
m := min(a.Len(), b.Len())
for i := 0; i < m; i++ {
if err = compare(a.Index(i), b.Index(i), i); err != nil {
return err
}
}
case reflect.Map:
keys := []string{}
for _, key := range a.MapKeys() {
keys = append(keys, key.String())
}
sort.Strings(keys)
for i, key := range keys {
if err = compare(a.MapIndex(reflect.ValueOf(key)), b.MapIndex(reflect.ValueOf(key)), i); err != nil {
return err
}
}
case reflect.String:
if a.String() != b.String() {
return fmt.Errorf("String fields not equal: %s != %s", a.String(), b.String())
}
default:
if a.CanInterface() && b.CanInterface() {
if a.Interface() != b.Interface() {
return fmt.Errorf("Elements are not equal: %#v != %#v", a.Interface(), b.Interface())
}
}
}
return nil
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@@ -0,0 +1,152 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"fmt"
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/management"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
)
// VCHDelete is the handler for deleting a VCH
type VCHDelete struct {
}
// VCHDatacenterDelete is the handler for deleting a VCH within a Datacenter
type VCHDatacenterDelete struct {
}
func (h *VCHDelete) Handle(params operations.DeleteTargetTargetVchVchIDParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDelete: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewDeleteTargetTargetVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
err = deleteVCH(op, d, validator, params.DeletionSpecification)
if err != nil {
return operations.NewDeleteTargetTargetVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewDeleteTargetTargetVchVchIDAccepted()
}
func (h *VCHDatacenterDelete) Handle(params operations.DeleteTargetTargetDatacenterDatacenterVchVchIDParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDelete: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
datacenter: &params.Datacenter,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewDeleteTargetTargetDatacenterDatacenterVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
err = deleteVCH(op, d, validator, params.DeletionSpecification)
if err != nil {
return operations.NewDeleteTargetTargetDatacenterDatacenterVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewDeleteTargetTargetDatacenterDatacenterVchVchIDAccepted()
}
func deleteVCH(op trace.Operation, d *data.Data, validator *validate.Validator, specification *models.DeletionSpecification) error {
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
vch, err := executor.NewVCHFromID(d.ID)
if err != nil {
return util.NewError(http.StatusNotFound, fmt.Sprintf("Failed to find VCH: %s", err))
}
err = validate.SetDataFromVM(validator.Context, validator.Session.Finder, vch, d)
if err != nil {
return util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to load VCH data: %s", err))
}
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
if err != nil {
return util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to load VCH data: %s", err))
}
// compare vch version and vic-machine version
installerBuild := version.GetBuild()
if vchConfig.Version == nil || !installerBuild.Equal(vchConfig.Version) {
op.Debugf("VCH version %q is different than API version %s", vchConfig.Version.ShortVersion(), installerBuild.ShortVersion())
}
deleteContainers, deleteVolumeStores := fromDeletionSpecification(specification)
err = executor.DeleteVCH(vchConfig, deleteContainers, deleteVolumeStores)
if err != nil {
return util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete VCH: %s", err))
}
return nil
}
func fromDeletionSpecification(specification *models.DeletionSpecification) (deleteContainers *management.DeleteContainers, deleteVolumeStores *management.DeleteVolumeStores) {
if specification != nil {
if specification.Containers != nil {
var dc management.DeleteContainers
switch *specification.Containers {
case models.DeletionSpecificationContainersAll:
dc = management.AllContainers
case models.DeletionSpecificationContainersOff:
dc = management.PoweredOffContainers
default:
panic("Deletion API handler received unexpected input")
}
deleteContainers = &dc
}
if specification.VolumeStores != nil {
var dv management.DeleteVolumeStores
switch *specification.VolumeStores {
case models.DeletionSpecificationVolumeStoresAll:
dv = management.AllVolumeStores
case models.DeletionSpecificationVolumeStoresNone:
dv = management.NoVolumeStores
default:
panic("Deletion API handler received unexpected input")
}
deleteVolumeStores = &dv
}
}
return
}

View File

@@ -0,0 +1,458 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"bytes"
"encoding/pem"
"fmt"
"net"
"net/http"
"strings"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/management"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/ip"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
"github.com/vmware/vic/pkg/vsphere/vm"
)
// VCHGet is the handler for inspecting a VCH
type VCHGet struct {
}
// VCHGet is the handler for inspecting a VCH within a Datacenter
type VCHDatacenterGet struct {
}
func (h *VCHGet) Handle(params operations.GetTargetTargetVchVchIDParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHGet: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
vch, err := getVCH(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewGetTargetTargetVchVchIDOK().WithPayload(vch)
}
func (h *VCHDatacenterGet) Handle(params operations.GetTargetTargetDatacenterDatacenterVchVchIDParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDatacenterGet: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
datacenter: &params.Datacenter,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
vch, err := getVCH(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDOK().WithPayload(vch)
}
func getVCH(op trace.Operation, d *data.Data, validator *validate.Validator) (*models.VCH, error) {
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
vch, err := executor.NewVCHFromID(d.ID)
if err != nil {
return nil, util.NewError(http.StatusNotFound, fmt.Sprintf("Failed to inspect VCH: %s", err))
}
err = validate.SetDataFromVM(validator.Context, validator.Session.Finder, vch, d)
if err != nil {
return nil, util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to load VCH data: %s", err))
}
model, err := vchToModel(op, vch, d, executor)
if err != nil {
return nil, util.WrapError(http.StatusInternalServerError, err)
}
return model, nil
}
func vchToModel(op trace.Operation, vch *vm.VirtualMachine, d *data.Data, executor *management.Dispatcher) (*models.VCH, error) {
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
if err != nil {
return nil, fmt.Errorf("Unable to retrieve VCH information: %s", err)
}
model := &models.VCH{}
model.Version = models.Version(vchConfig.Version.ShortVersion())
model.Name = vchConfig.Name
model.Debug = int64(vchConfig.Diagnostics.DebugLevel)
// compute
model.Compute = &models.VCHCompute{
CPU: &models.VCHComputeCPU{
Limit: asMHz(d.ResourceLimits.VCHCPULimitsMHz),
Reservation: asMHz(d.ResourceLimits.VCHCPUReservationsMHz),
Shares: asShares(d.ResourceLimits.VCHCPUShares),
},
Memory: &models.VCHComputeMemory{
Limit: asMiB(d.ResourceLimits.VCHMemoryLimitsMB),
Reservation: asMiB(d.ResourceLimits.VCHMemoryReservationsMB),
Shares: asShares(d.ResourceLimits.VCHMemoryShares),
},
Resource: &models.ManagedObject{
ID: mobidToID(vchConfig.Container.ComputeResources[0].String()),
},
}
// network
model.Network = &models.VCHNetwork{
Bridge: &models.VCHNetworkBridge{
PortGroup: &models.ManagedObject{
ID: mobidToID(vchConfig.ExecutorConfig.Networks[vchConfig.Network.BridgeNetwork].Network.Common.ID),
},
IPRange: asCIDR(vchConfig.Network.BridgeIPRange),
},
Client: asNetwork(vchConfig.ExecutorConfig.Networks["client"]),
Management: asNetwork(vchConfig.ExecutorConfig.Networks["management"]),
Public: asNetwork(vchConfig.ExecutorConfig.Networks["public"]),
}
containerNetworks := make([]*models.ContainerNetwork, 0, len(vchConfig.Network.ContainerNetworks))
for key, value := range vchConfig.Network.ContainerNetworks {
if key != "bridge" {
containerNetworks = append(containerNetworks, &models.ContainerNetwork{
Alias: value.Name,
PortGroup: &models.ManagedObject{
ID: mobidToID(value.Common.ID),
},
Nameservers: *asIPAddresses(&value.Nameservers),
Gateway: &models.Gateway{
Address: asIPAddress(value.Gateway.IP),
RoutingDestinations: []models.CIDR{asCIDR(&value.Gateway)},
},
IPRanges: *rangesAsIPRanges(&value.Pools),
Firewall: value.TrustLevel.String(),
})
}
}
model.Network.Container = containerNetworks
// storage
scratchSize := int(vchConfig.Storage.ScratchSize)
model.Storage = &models.VCHStorage{
BaseImageSize: asKB(&scratchSize),
}
volumeLocations := make([]*models.VCHStorageVolumeStoresItems0, 0, len(vchConfig.Storage.VolumeLocations))
for label, path := range vchConfig.Storage.VolumeLocations {
parsedPath := object.DatastorePath{}
parsed := parsedPath.FromString(path.Path)
if parsed {
path.Path = parsedPath.Path
}
volume := models.VCHStorageVolumeStoresItems0{Datastore: path.String(), Label: label}
volumeLocations = append(volumeLocations, &volume)
}
model.Storage.VolumeStores = volumeLocations
imageStores := make([]string, 0, len(vchConfig.Storage.ImageStores))
for _, value := range vchConfig.Storage.ImageStores {
imageStores = append(imageStores, value.String())
}
model.Storage.ImageStores = imageStores
// auth
model.Auth = &models.VCHAuth{
Client: &models.VCHAuthClient{},
}
if vchConfig.Certificate.HostCertificate != nil {
model.Auth.Server = &models.VCHAuthServer{
Certificate: asPemCertificate(vchConfig.Certificate.HostCertificate.Cert),
}
}
model.Auth.Client.CertificateAuthorities = asPemCertificates(vchConfig.Certificate.CertificateAuthorities)
// endpoint
model.Endpoint = &models.VCHEndpoint{
Memory: asMiB(&d.MemoryMB),
CPU: &models.VCHEndpointCPU{
Sockets: int64(d.NumCPUs),
},
OperationsCredentials: &models.VCHEndpointOperationsCredentials{
User: vchConfig.Connection.Username,
// Password intentionally excluded from GET responses for security reasons!
},
}
// registry
model.Registry = &models.VCHRegistry{
Insecure: vchConfig.Registry.InsecureRegistries,
Whitelist: vchConfig.Registry.RegistryWhitelist,
CertificateAuthorities: asPemCertificates(vchConfig.Certificate.RegistryCertificateAuthorities),
ImageFetchProxy: asImageFetchProxy(vchConfig.ExecutorConfig.Sessions[config.VicAdminService], config.VICAdminHTTPProxy, config.VICAdminHTTPSProxy),
}
// runtime
model.Runtime = &models.VCHRuntime{}
installerVer := version.GetBuild()
upgradeStatus := upgradeStatusMessage(op, vch, installerVer, vchConfig.Version)
model.Runtime.UpgradeStatus = upgradeStatus
powerState, err := vch.PowerState(op)
if err != nil {
powerState = "error"
}
model.Runtime.PowerState = string(powerState)
model.Runtime.DockerHost, model.Runtime.AdminPortal = getAddresses(vchConfig)
// syslog_addr: syslog server address
if syslogConfig := vchConfig.Diagnostics.SysLogConfig; syslogConfig != nil {
model.SyslogAddr = strfmt.URI(syslogConfig.Network + "://" + syslogConfig.RAddr)
}
model.Container = &models.VCHContainer{}
if vchConfig.ContainerNameConvention != "" {
model.Container.NameConvention = vchConfig.ContainerNameConvention
}
return model, nil
}
func asBytes(value *int, units string) *models.ValueBytes {
if value == nil || *value == 0 {
return nil
}
return &models.ValueBytes{
Value: models.Value{
Value: int64(*value),
Units: units,
},
}
}
func asMiB(value *int) *models.ValueBytes {
return asBytes(value, models.ValueBytesUnitsMiB)
}
func asBytesMetric(value *int, units string) *models.ValueBytesMetric {
if value == nil || *value == 0 {
return nil
}
return &models.ValueBytesMetric{
Value: models.Value{
Value: int64(*value),
Units: units,
},
}
}
func asKB(value *int) *models.ValueBytesMetric {
return asBytesMetric(value, models.ValueBytesMetricUnitsKB)
}
func asMHz(value *int) *models.ValueHertz {
if value == nil || *value == 0 {
return nil
}
return &models.ValueHertz{
Value: models.Value{
Value: int64(*value),
Units: models.ValueHertzUnitsMHz,
},
}
}
func asShares(shares *types.SharesInfo) *models.Shares {
if shares == nil {
return nil
}
return &models.Shares{
Level: string(shares.Level),
Number: int64(shares.Shares),
}
}
func asIPAddress(address net.IP) models.IPAddress {
return models.IPAddress(address.String())
}
func asIPAddresses(addresses *[]net.IP) *[]models.IPAddress {
m := make([]models.IPAddress, 0, len(*addresses))
for _, value := range *addresses {
m = append(m, asIPAddress(value))
}
return &m
}
func asCIDR(network *net.IPNet) models.CIDR {
if network == nil {
return models.CIDR("")
}
return models.CIDR(network.String())
}
func asCIDRs(networks *[]net.IPNet) *[]models.CIDR {
m := make([]models.CIDR, 0, len(*networks))
for _, value := range *networks {
m = append(m, asCIDR(&value))
}
return &m
}
func asIPRange(network *net.IPNet) models.IPRange {
if network == nil {
return models.IPRange("")
}
return models.IPRange(models.CIDR(network.String()))
}
func asIPRanges(networks *[]net.IPNet) *[]models.IPRange {
m := make([]models.IPRange, 0, len(*networks))
for _, value := range *networks {
m = append(m, asIPRange(&value))
}
return &m
}
func rangesAsIPRanges(networks *[]ip.Range) *[]models.IPRange {
m := make([]models.IPRange, 0, len(*networks))
for _, value := range *networks {
m = append(m, asIPRange(value.Network()))
}
return &m
}
func asNetwork(network *executor.NetworkEndpoint) *models.Network {
if network == nil {
return nil
}
m := &models.Network{
PortGroup: &models.ManagedObject{
ID: mobidToID(network.Network.Common.ID),
},
Nameservers: *asIPAddresses(&network.Network.Nameservers),
}
if network.Network.Gateway.IP != nil {
m.Gateway = &models.Gateway{
Address: asIPAddress(network.Network.Gateway.IP),
RoutingDestinations: *asCIDRs(&network.Network.Destinations),
}
}
return m
}
func mobidToID(mobid string) string {
moref := new(types.ManagedObjectReference)
ok := moref.FromString(mobid)
if !ok {
return "" // TODO (#6717): Handle? (We probably don't want to let this fail the request, but may want to convey that something unexpected happened.)
}
return moref.Value
}
func asPemCertificates(certificates []byte) []*models.X509Data {
var buf bytes.Buffer
m := make([]*models.X509Data, 0)
for c := &certificates; len(*c) > 0; {
b, rest := pem.Decode(*c)
err := pem.Encode(&buf, b)
if err != nil {
continue // TODO (#6716): Handle? (We probably don't want to let this fail the request, but may want to convey that something unexpected happened.)
}
m = append(m, &models.X509Data{
Pem: models.PEM(buf.String()),
})
c = &rest
}
return m
}
func asPemCertificate(certificates []byte) *models.X509Data {
m := asPemCertificates(certificates)
if len(m) > 1 {
// TODO (#6716): Error? (We probably don't want to let this fail the request, but may want to convey that something unexpected happened.)
}
return m[0]
}
func asImageFetchProxy(sessionConfig *executor.SessionConfig, http, https string) *models.VCHRegistryImageFetchProxy {
var httpProxy, httpsProxy strfmt.URI
for _, env := range sessionConfig.Cmd.Env {
if strings.HasPrefix(env, http+"=") {
httpProxy = strfmt.URI(strings.SplitN(env, "=", 2)[1])
}
if strings.HasPrefix(env, https+"=") {
httpsProxy = strfmt.URI(strings.SplitN(env, "=", 2)[1])
}
}
if httpProxy == "" && httpsProxy == "" {
return nil
}
return &models.VCHRegistryImageFetchProxy{HTTP: httpProxy, HTTPS: httpsProxy}
}

View File

@@ -0,0 +1,123 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"fmt"
"net/http"
"path"
"github.com/go-openapi/runtime/middleware"
"github.com/vmware/vic/lib/apiservers/service/models"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/management"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
"github.com/vmware/vic/pkg/vsphere/vm"
)
// VCHListGet is the handler for listing VCHs
type VCHListGet struct {
}
// VCHDatacenterListGet is the handler for listing VCHs within a Datacenter
type VCHDatacenterListGet struct {
}
func (h *VCHListGet) Handle(params operations.GetTargetTargetVchParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHListGet")
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
computeResource: params.ComputeResource,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
vchs, err := listVCHs(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewGetTargetTargetVchOK().WithPayload(operations.GetTargetTargetVchOKBody{Vchs: vchs})
}
func (h *VCHDatacenterListGet) Handle(params operations.GetTargetTargetDatacenterDatacenterVchParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDatacenterListGet")
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
datacenter: &params.Datacenter,
computeResource: params.ComputeResource,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
vchs, err := listVCHs(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}
return operations.NewGetTargetTargetVchOK().WithPayload(operations.GetTargetTargetVchOKBody{Vchs: vchs})
}
func listVCHs(op trace.Operation, d *data.Data, validator *validate.Validator) ([]*models.VCHListItem, error) {
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
vchs, err := executor.SearchVCHs(validator.ClusterPath)
if err != nil {
return nil, util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to search VCHs in %s: %s", validator.ResourcePoolPath, err))
}
return vchsToModels(op, vchs, executor), nil
}
func vchsToModels(op trace.Operation, vchs []*vm.VirtualMachine, executor *management.Dispatcher) []*models.VCHListItem {
installerVer := version.GetBuild()
payload := make([]*models.VCHListItem, 0)
for _, vch := range vchs {
var version *version.Build
var dockerHost string
var adminPortal string
if vchConfig, err := executor.GetNoSecretVCHConfig(vch); err == nil {
version = vchConfig.Version
dockerHost, adminPortal = getAddresses(vchConfig)
}
name := path.Base(vch.InventoryPath)
model := &models.VCHListItem{ID: vch.Reference().Value, Name: name, AdminPortal: adminPortal, DockerHost: dockerHost}
if version != nil {
model.Version = version.ShortVersion()
model.UpgradeStatus = upgradeStatusMessage(op, vch, installerVer, version)
}
payload = append(payload, model)
}
return payload
}

View File

@@ -0,0 +1,183 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"bytes"
"fmt"
"net/http"
"sort"
"strings"
"github.com/go-openapi/runtime/middleware"
"github.com/vmware/vic/lib/apiservers/service/restapi/handlers/util"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/management"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/lib/install/vchlog"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/datastore"
)
// VCHLogGet is the handler for getting the log messages for a VCH
type VCHLogGet struct {
}
// VCHDatacenterLogGet is the handler for getting the log messages for a VCH within a Datacenter
type VCHDatacenterLogGet struct {
}
func (h *VCHLogGet) Handle(params operations.GetTargetTargetVchVchIDLogParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHLogGet: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
helper, err := getDatastoreHelper(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
logFilePaths, err := getAllLogFilePaths(op, helper)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
output, err := getContentFromLogFiles(op, helper, logFilePaths)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
return operations.NewGetTargetTargetVchVchIDLogOK().WithPayload(output)
}
func (h *VCHDatacenterLogGet) Handle(params operations.GetTargetTargetDatacenterDatacenterVchVchIDLogParams, principal interface{}) middleware.Responder {
op := trace.FromContext(params.HTTPRequest.Context(), "VCHDatacenterLogGet: %s", params.VchID)
b := buildDataParams{
target: params.Target,
thumbprint: params.Thumbprint,
datacenter: &params.Datacenter,
vchID: &params.VchID,
}
d, validator, err := buildDataAndValidateTarget(op, b, principal)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
helper, err := getDatastoreHelper(op, d, validator)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
logFilePaths, err := getAllLogFilePaths(op, helper)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
output, err := getContentFromLogFiles(op, helper, logFilePaths)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(err.Error())
}
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogOK().WithPayload(output)
}
// getDatastoreHelper validates the VCH and returns the datastore helper for the VCH. It errors when validation fails or when datastore is not ready
func getDatastoreHelper(op trace.Operation, d *data.Data, validator *validate.Validator) (*datastore.Helper, error) {
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
vch, err := executor.NewVCHFromID(d.ID)
if err != nil {
return nil, util.NewError(http.StatusNotFound, fmt.Sprintf("Unable to find VCH %s: %s", d.ID, err))
}
if err := validate.SetDataFromVM(validator.Context, validator.Session.Finder, vch, d); err != nil {
return nil, util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to load VCH data: %s", err))
}
// Relative path of datastore folder
vmPath, err := vch.VMPathNameAsURL(op)
if err != nil {
return nil, util.NewError(http.StatusNotFound, fmt.Sprintf("Unable to retrieve VCH datastore information: %s", err))
}
// Get VCH datastore object
ds, err := validator.Session.Finder.Datastore(validator.Context, vmPath.Host)
if err != nil {
return nil, util.NewError(http.StatusNotFound, fmt.Sprintf("Datastore folder not found for VCH %s: %s", d.ID, err))
}
// Create a new datastore helper for file finding
helper, err := datastore.NewHelper(op, validator.Session, ds, vmPath.Path)
if err != nil {
return nil, fmt.Errorf("Unable to get datastore helper: %s", err)
}
return helper, nil
}
// getAllLogFilePaths returns a list of all log file paths under datastore folder, errors out when no log file found
func getAllLogFilePaths(op trace.Operation, helper *datastore.Helper) ([]string, error) {
res, err := helper.Ls(op, "")
if err != nil {
return nil, fmt.Errorf("Unable to list all files under datastore: %s", err)
}
var paths []string
for _, f := range res.File {
path := f.GetFileInfo().Path
if strings.HasPrefix(path, vchlog.LogFilePrefix) && strings.HasSuffix(path, vchlog.LogFileSuffix) {
paths = append(paths, path)
}
}
if len(paths) == 0 {
return nil, util.NewError(http.StatusNotFound, "No log file available in datastore folder")
}
return paths, nil
}
// getContentFromLogFile downloads all log files in the list, concatenates the content of each log file and outputs a string of contents
func getContentFromLogFiles(op trace.Operation, helper *datastore.Helper, paths []string) (string, error) {
var buffer bytes.Buffer
// sort log files based on timestamp
sort.Strings(paths)
for _, p := range paths {
reader, err := helper.Download(op, p)
if err != nil {
return "", fmt.Errorf("Unable to download log file %s: %s", p, err)
}
if _, err := buffer.ReadFrom(reader); err != nil {
return "", fmt.Errorf("Error reading from log file %s: %s", p, err)
}
}
return string(buffer.Bytes()), nil
}

View File

@@ -0,0 +1,30 @@
// Copyright 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package handlers
import (
"github.com/go-openapi/runtime/middleware"
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
"github.com/vmware/vic/pkg/version"
)
// VersionGet is the handler for accessing the version information for the service
type VersionGet struct {
}
func (h *VersionGet) Handle(params operations.GetVersionParams) middleware.Responder {
return operations.NewGetVersionOK().WithPayload(version.GetBuild().ShortVersion())
}