Skip to content

Commit

Permalink
Implement REST API handler for getting VCH creation logs (#6524)
Browse files Browse the repository at this point in the history
* vch log API handler stub added

* vch log handler stub added

* VCH log handler implemented

* whitespaces

* whitespace

* whitespace

* cleaned up integration test temp files

* log API handler implemented, integration test added

* remove log file download function from logger

* whitespace

* make the log files automatically sort by timestamp

* .log filename suffix added to log files; sort files based on timestamp

* sort log files based on timestamp

* modified error message for finding VCH from ID

* remove unused package declaration

* fixed error code when provided VCH ID does not exist

* VCH-log integration test md modified

* 404 test for log API handler added after log file's removed

* log handler integration test refactored

* use status code constants from http package; minor code improvements in log API handler

* minor bug fixes

* const declaration

* minor bug fix in common.go
  • Loading branch information
AngieCris authored and zjs committed Nov 20, 2017
1 parent ecd4a8f commit a296fc2
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 17 deletions.
6 changes: 6 additions & 0 deletions lib/apiservers/service/restapi/configure_vic_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,15 @@ func configureAPI(api *operations.VicMachineAPI) http.Handler {
// 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")
Expand Down
11 changes: 6 additions & 5 deletions lib/apiservers/service/restapi/handlers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package handlers
import (
"context"
"fmt"
"net/http"
"net/url"

"github.com/vmware/govmomi/object"
Expand Down Expand Up @@ -48,14 +49,14 @@ func buildData(ctx context.Context, url url.URL, user string, pass string, thumb
if datacenter != nil {
validator, err := validateTarget(ctx, &d)
if err != nil {
return nil, util.WrapError(500, err)
return nil, util.WrapError(http.StatusInternalServerError, err)
}

datacenterManagedObjectReference := types.ManagedObjectReference{Type: "Datacenter", Value: *datacenter}

datacenterObject, err := validator.Session.Finder.ObjectReference(ctx, datacenterManagedObjectReference)
if err != nil {
return nil, util.WrapError(404, err)
return nil, util.WrapError(http.StatusNotFound, err)
}

d.Target.URL.Path = datacenterObject.(*object.Datacenter).InventoryPath
Expand Down Expand Up @@ -130,18 +131,18 @@ func getVCHConfig(op trace.Operation, d *data.Data) (*config.VirtualContainerHos
// TODO(jzt): abstract some of this boilerplate into helpers
validator, err := validateTarget(op.Context, d)
if err != nil {
return nil, util.WrapError(400, err)
return nil, util.WrapError(http.StatusBadRequest, err)
}

executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
vch, err := executor.NewVCHFromID(d.ID)
if err != nil {
return nil, util.NewError(500, fmt.Sprintf("failed to create VCH %s: %s", d.ID, err))
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(500, fmt.Sprintf("Failed to load VCH data: %s", err))
return nil, util.NewError(http.StatusInternalServerError, fmt.Sprintf("Failed to load VCH data: %s", err))
}

vchConfig, err := executor.GetNoSecretVCHConfig(vch)
Expand Down
200 changes: 200 additions & 0 deletions lib/apiservers/service/restapi/handlers/vch_log_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// 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"
"context"
"fmt"
"net/http"
"net/url"
"sort"
"strings"

"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/vsphere/datastore"
)

const (
logFilePrefix = "vic-machine" // logFilePrefix is the prefix for file names of all vic-machine log files
logFileSuffix = ".log" // logFileSuffix is the suffix for file names of all vic-machine log files
)

// 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 {
d, err := buildData(params.HTTPRequest.Context(),
url.URL{Host: params.Target},
principal.(Credentials).user,
principal.(Credentials).pass,
params.Thumbprint,
nil,
nil)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

d.ID = params.VchID
op := trace.NewOperation(params.HTTPRequest.Context(), "vch: %s", params.VchID)

helper, err := getDatastoreHelper(op.Context, d)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

logFilePaths, err := getAllLogFilePaths(op.Context, helper)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

output, err := getContentFromLogFiles(op.Context, helper, logFilePaths)
if err != nil {
return operations.NewGetTargetTargetVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

return operations.NewGetTargetTargetVchVchIDLogOK().WithPayload(output)
}

func (h *VCHDatacenterLogGet) Handle(params operations.GetTargetTargetDatacenterDatacenterVchVchIDLogParams, principal interface{}) middleware.Responder {
d, err := buildData(params.HTTPRequest.Context(),
url.URL{Host: params.Target},
principal.(Credentials).user,
principal.(Credentials).pass,
params.Thumbprint,
&params.Datacenter,
nil)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

d.ID = params.VchID
op := trace.NewOperation(params.HTTPRequest.Context(), "vch: %s", params.VchID)

helper, err := getDatastoreHelper(op.Context, d)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

logFilePaths, err := getAllLogFilePaths(op.Context, helper)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: err.Error()})
}

output, err := getContentFromLogFiles(op.Context, helper, logFilePaths)
if err != nil {
return operations.NewGetTargetTargetDatacenterDatacenterVchVchIDLogDefault(util.StatusCode(err)).WithPayload(&models.Error{Message: 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(ctx context.Context, d *data.Data) (*datastore.Helper, error) {
// TODO (angiew): abstract some of the boilerplate into helpers in common.go
validator, err := validateTarget(ctx, d)
if err != nil {
return nil, util.WrapError(http.StatusBadRequest, err)
}

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))
}

// Get VCH configuration
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
if err != nil {
return nil, fmt.Errorf("Unable to retrieve VCH information: %s", err)
}

// Relative path of datastore folder
vmPath := vchConfig.ImageStores[0].Path

// Get VCH datastore object
ds, err := validator.Session.Finder.Datastore(validator.Context, vchConfig.ImageStores[0].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(ctx, validator.Session, ds, vmPath)
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(ctx context.Context, helper *datastore.Helper) ([]string, error) {
res, err := helper.Ls(ctx, "")
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, logFilePrefix) && strings.HasSuffix(path, 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(ctx context.Context, 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(ctx, 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
}
37 changes: 37 additions & 0 deletions lib/apiservers/service/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,21 @@
}
}
},
"/target/{target}/vch/{vchId}/log": {
"get": {
"summary": "Access log messages for a VCH",
"description": "Making a `GET` request on /log under a VCH resource will return the log messages for vic-machine processes on a VCH",
"parameters": [
{ "$ref": "#/parameters/target" },
{ "$ref": "#/parameters/vch-id" },
{ "$ref": "#/parameters/thumbprint" }
],
"responses": {
"200": { "$ref": "#/responses/vch-log" },
"default": { "$ref": "#/responses/error" }
}
}
},
"/target/{target}/datacenter/{datacenter}": {
"get": {
"summary": "Show information about the specified vSphere resources",
Expand Down Expand Up @@ -353,6 +368,22 @@
"default": { "$ref": "#/responses/error" }
}
}
},
"/target/{target}/datacenter/{datacenter}/vch/{vchId}/log": {
"get": {
"summary": "Access log messages for a VCH in a particular datacenter",
"description": "Making a `GET` request on /log under a VCH resource will return the log messages for vic-machine processes on a VCH.",
"parameters": [
{ "$ref": "#/parameters/target" },
{ "$ref": "#/parameters/datacenter" },
{ "$ref": "#/parameters/vch-id" },
{ "$ref": "#/parameters/thumbprint" }
],
"responses": {
"200": { "$ref": "#/responses/vch-log" },
"default": { "$ref": "#/responses/error" }
}
}
}
},
"definitions": {
Expand Down Expand Up @@ -897,6 +928,12 @@
"description": "A VCH host certificate",
"schema": { "$ref": "#/definitions/PEM" }
},
"vch-log": {
"description": "Log messages for a VCH",
"schema": {
"type": "string"
}
},
"vsphere-task": {
"description": "A vSphere task",
"schema": {
Expand Down
10 changes: 5 additions & 5 deletions lib/install/management/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ func (d *Dispatcher) CreateVCH(conf *config.VirtualContainerHostConfigSpec, sett

// send the signal to VCH logger to indicate VCH datastore path is ready
datastoreReadySignal := vchlog.DatastoreReadySignal{
Datastore: d.session.Datastore,
LogFileName: "vic-machine-create",
Operation: trace.NewOperation(d.ctx, "vic-machine create"),
VMPathName: d.vmPathName,
Timestamp: time.Now().UTC().Format(timeFormat),
Datastore: d.session.Datastore,
Name: "create",
Operation: trace.NewOperation(d.ctx, "create"),
VMPathName: d.vmPathName,
Timestamp: time.Now().UTC().Format(timeFormat),
}
vchlog.Signal(datastoreReadySignal)

Expand Down
13 changes: 7 additions & 6 deletions lib/install/vchlog/vchlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ import (

// DatastoreReadySignal serves as a signal struct indicating datastore folder path is available
// Datastore: the govmomi datastore object
// Name: the name of the vic-machine process that sends the signal (e.g. "create", "inspect")
// LogFileName: the filename of the destination path on datastore
// Context: the caller context when sending the signal
// VMPathName: the datastore path
type DatastoreReadySignal struct {
Datastore *object.Datastore
LogFileName string
Operation trace.Operation
VMPathName string
Timestamp string
Datastore *object.Datastore
Name string
Operation trace.Operation
VMPathName string
Timestamp string
}

// pipe: the streaming readwriter pipe to hold log messages
Expand All @@ -50,7 +51,7 @@ func Init() {
func Run() {
sig := <-signalChan
// suffix the log file name with caller operation ID and timestamp
logFileName := sig.LogFileName + "_time_" + sig.Timestamp + "_op_" + sig.Operation.ID()
logFileName := "vic-machine" + "_" + sig.Timestamp + "_" + sig.Name + "_" + sig.Operation.ID() + ".log"
sig.Datastore.Upload(sig.Operation.Context, pipe, path.Join(sig.VMPathName, logFileName), nil)
}

Expand Down
26 changes: 26 additions & 0 deletions tests/test-cases/Group23-VIC-Machine-Service/23-05-VCH-Logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Test 23-05 - VCH Logs
=======

# Purpose:
To verify vic-machine-server can provide logs for a VCH host when available

# References:
[1 - VIC Machine Service API Design Doc - VCH Certificate](../../../doc/design/vic-machine/service.md)

# Environment:
This test requires that a vSphere server is running and available, where VCH can be deployed.

# Test Steps:
1. Deloy a VCH into the test environment
2. Verify that the creation log is available after the VCH is created using the vic-machine-service
3. Verify that the creation log is available for its particular datacenter using the vic-machine-service
4. Delete the log file from VCH datastore folder
5. Verify that creation log is unavailable (404) using the vic-machine service
6. Verify that creation log is unavailable (404) for its particular datacenter using the vic-machine-service

# Expected Outcome:
* Step 2-3 should succeed and output should contain log message that the creation is completed successfully
* Step 5-6 should error with a 404 (not found) as no log file exists

# Possible Problems:
None
Loading

0 comments on commit a296fc2

Please sign in to comment.