Skip to content

Commit

Permalink
Add virt-api guestosinfo subresource
Browse files Browse the repository at this point in the history
Subresource queries virt-handler and
get the guest agent data

Also the neccesary metadata has been added to
agentinfo type.

The client-go has been updated to connect to endpoint.

Signed-off-by: Petr Kotas <[email protected]>
  • Loading branch information
petrkotas committed Feb 25, 2020
1 parent 7f1c78d commit c5ae5d3
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 1 deletion.
28 changes: 27 additions & 1 deletion api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3726,6 +3726,24 @@
],
"summary": "Get guest agent os information",
"operationId": "guestosinfo",
"parameters": [
{
"pattern": "[a-z0-9][a-z0-9\\-]*",
"type": "string",
"description": "Object name and auth scope, such as for teams and projects",
"name": "namespace",
"in": "path",
"required": true
},
{
"pattern": "[a-z0-9][a-z0-9\\-]*",
"type": "string",
"description": "Name of the resource",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -6227,6 +6245,10 @@
"v1.VirtualMachineInstanceGuestAgentInfo": {
"description": "VirtualMachineInstanceGuestAgentInfo represents information from the installed guest agent",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"fsInfo": {
"description": "FSInfo is a guest os filesystem information containing the disk mapping and disk mounts with usage",
"$ref": "#/definitions/v1.VirtualMachineInstanceFileSystemInfo"
Expand All @@ -6239,6 +6261,10 @@
"description": "Hostname represents FQDN of a guest",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"os": {
"description": "OS contains the guest operating system information",
"$ref": "#/definitions/v1.VirtualMachineInstanceGuestOSInfo"
Expand Down Expand Up @@ -7204,4 +7230,4 @@
"BearerToken": []
}
]
}
}
5 changes: 5 additions & 0 deletions pkg/virt-api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ func (app *virtAPIApp) composeSubresources() {

subws.Route(subws.GET(rest.ResourcePath(subresourcesvmiGVR)+rest.SubResourcePath("guestosinfo")).
To(subresourceApp.GuestOSInfo).
Param(rest.NamespaceParam(subws)).Param(rest.NameParam(subws)).
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON).
Operation("guestosinfo").
Expand Down Expand Up @@ -354,6 +355,10 @@ func (app *virtAPIApp) composeSubresources() {
Name: "virtualmachines/migrate",
Namespaced: true,
},
{
Name: "virtualmachineinstances/guestosinfo",
Namespaced: true,
},
}

response.WriteAsJson(list)
Expand Down
41 changes: 41 additions & 0 deletions pkg/virt-api/rest/subresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,3 +772,44 @@ func writeError(error *errors.StatusError, response *restful.Response) {
log.Log.Reason(err).Error("Failed to write http response.")
}
}

// GuestOSInfo handles the subresource for providing VM guest agent information
func (app *SubresourceAPIApp) GuestOSInfo(request *restful.Request, response *restful.Response) {
validate := func(vmi *v1.VirtualMachineInstance) *errors.StatusError {
if vmi == nil || vmi.Status.Phase != v1.Running {
return errors.NewConflict(v1.Resource("virtualmachineinstance"), vmi.Name, fmt.Errorf("VMI is not running"))
}
condManager := controller.NewVirtualMachineInstanceConditionManager()
if !condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected) {
return errors.NewConflict(v1.Resource("virtualmachineinstance"), vmi.Name, fmt.Errorf("VMI does not have guest agent connected"))
}
return nil
}
getURL := func(vmi *v1.VirtualMachineInstance, conn kubecli.VirtHandlerConn) (string, error) {
return conn.GuestInfoURI(vmi)
}

log.Log.Infof("Calling get url on handler for")
_, url, conn, err := app.prepareConnection(request, validate, getURL)
if err != nil {
response.WriteError(http.StatusInternalServerError, err)
log.Log.Errorf("Cannot prepare connection %s", err.Error())
return
}

resp, conErr := conn.Get(url, app.handlerTLSConfiguration)
if conErr != nil {
log.Log.Errorf("Cannot GET request %s", conErr.Error())
response.WriteError(http.StatusInternalServerError, conErr)
return
}

guestInfo := v1.VirtualMachineInstanceGuestAgentInfo{}
if err := json.Unmarshal([]byte(resp), &guestInfo); err != nil {
log.Log.Reason(err).Error("error unmarshalling guest agent response")
response.WriteError(http.StatusInternalServerError, err)
return
}

response.WriteEntity(guestInfo)
}
67 changes: 67 additions & 0 deletions pkg/virt-api/rest/subresource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,73 @@ var _ = Describe("VirtualMachineInstance Subresources", func() {
})
})

Context("Subresource api - Guest OS Info", func() {
It("should fail when the VMI does not exist", func(done Done) {
request.PathParameters()["name"] = "testvm"
request.PathParameters()["namespace"] = "default"

server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha3/namespaces/default/virtualmachineinstances/testvm"),
ghttp.RespondWithJSONEncoded(http.StatusNotFound, nil),
),
)

app.GuestOSInfo(request, response)

Expect(response.Error()).To(HaveOccurred(), "Response should indicate VM not found")
Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
close(done)
})

It("should fail when the VMI is not running", func(done Done) {
request.PathParameters()["name"] = "testvm"
request.PathParameters()["namespace"] = "default"

vmi := v1.VirtualMachineInstance{}

server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha3/namespaces/default/virtualmachineinstances/testvm"),
ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
),
)

app.GuestOSInfo(request, response)

Expect(response.Error()).To(HaveOccurred())
Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
Expect(response.Error().Error()).To(ContainSubstring("VMI is not running"))
close(done)
})

It("should fail when VMI does not have agent connected", func(done Done) {
request.PathParameters()["name"] = "testvm"
request.PathParameters()["namespace"] = "default"

vmi := v1.VirtualMachineInstance{
Status: v1.VirtualMachineInstanceStatus{
Phase: v1.Running,
Conditions: []v1.VirtualMachineInstanceCondition{},
},
}

server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha3/namespaces/default/virtualmachineinstances/testvm"),
ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
),
)

app.GuestOSInfo(request, response)

Expect(response.Error()).To(HaveOccurred())
Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
Expect(response.Error().Error()).To(ContainSubstring("VMI does not have guest agent connected"))
close(done)
})
})

Context("StateChange JSON", func() {
It("should create a stop request if status exists", func() {
uid := uuid.NewUUID()
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions staging/src/kubevirt.io/client-go/api/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions staging/src/kubevirt.io/client-go/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,7 @@ type RestartOptions struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true
type VirtualMachineInstanceGuestAgentInfo struct {
metav1.TypeMeta `json:",inline"`
// GAVersion is a version of currently installed guest agent
GAVersion string `json:"guestAgentVersion,omitempty"`
// Hostname represents FQDN of a guest
Expand Down
1 change: 1 addition & 0 deletions staging/src/kubevirt.io/client-go/kubecli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ go_library(
"//staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned:go_default_library",
"//staging/src/kubevirt.io/client-go/generated/network-attachment-definition-client/clientset/versioned:go_default_library",
"//staging/src/kubevirt.io/client-go/generated/prometheus-operator/clientset/versioned:go_default_library",
"//staging/src/kubevirt.io/client-go/log:go_default_library",
"//staging/src/kubevirt.io/client-go/subresources:go_default_library",
"//staging/src/kubevirt.io/client-go/util:go_default_library",
"//staging/src/kubevirt.io/client-go/version:go_default_library",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,17 @@ func (_mr *_MockVirtualMachineInstanceInterfaceRecorder) Unpause(arg0 interface{
return _mr.mock.ctrl.RecordCall(_mr.mock, "Unpause", arg0)
}

func (_m *MockVirtualMachineInstanceInterface) GuestOsInfo(name string) (v114.VirtualMachineInstanceGuestAgentInfo, error) {
ret := _m.ctrl.Call(_m, "GuestOsInfo", name)
ret0, _ := ret[0].(v114.VirtualMachineInstanceGuestAgentInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}

func (_mr *_MockVirtualMachineInstanceInterfaceRecorder) GuestOsInfo(arg0 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "GuestOsInfo", arg0)
}

// Mock of ReplicaSetInterface interface
type MockReplicaSetInterface struct {
ctrl *gomock.Controller
Expand Down
1 change: 1 addition & 0 deletions staging/src/kubevirt.io/client-go/kubecli/kubevirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ type VirtualMachineInstanceInterface interface {
VNC(name string) (StreamInterface, error)
Pause(name string) error
Unpause(name string) error
GuestOsInfo(name string) (v1.VirtualMachineInstanceGuestAgentInfo, error)
}

type ReplicaSetInterface interface {
Expand Down
35 changes: 35 additions & 0 deletions staging/src/kubevirt.io/client-go/kubecli/vmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"k8s.io/client-go/rest"

v1 "kubevirt.io/client-go/api/v1"
"kubevirt.io/client-go/log"
"kubevirt.io/client-go/subresources"
)

Expand Down Expand Up @@ -414,3 +415,37 @@ func enrichError(httpErr error, resp *http.Response) error {
}
return httpErr
}

func (v *vmis) GuestOsInfo(name string) (v1.VirtualMachineInstanceGuestAgentInfo, error) {
guestInfo := v1.VirtualMachineInstanceGuestAgentInfo{}
uri := fmt.Sprintf(vmiSubresourceURL, v1.ApiStorageVersion, v.namespace, name, "guestosinfo")

// WORKAROUND:
// When doing v.restClient.Get().RequestURI(uri).Do().Into(guestInfo)
// k8s client-go requires the object to have metav1.ObjectMeta inlined and deepcopy generated
// without deepcopy the Into does not work.
// With metav1.ObjectMeta added the openapi validation fails on pkg/virt-api/api.go:310
// When returning object the openapi schema validation fails on invalid type field for
// metav1.ObjectMeta.CreationTimestamp of type time (the schema validation fails, not the object validation).
// In our schema we implemented workaround to have multiple types for this field (null, string), which is causing issues
// with deserialization.
// The issue popped up for this code since this is the first time anything is returned.
//
// The issue is present because KubeVirt have to support multiple k8s version. In newer k8s version (1.17+)
// this issue should be solved.
// This workaround can go away once the least supported k8s version is the working one.
// The issue has been described in: https://github.com/kubevirt/kubevirt/issues/3059
res := v.restClient.Get().RequestURI(uri).Do()
rawInfo, err := res.Raw()
if err != nil {
log.Log.Errorf("Cannot retrieve GuestOSInfo: %s", err.Error())
return guestInfo, err
}

err = json.Unmarshal(rawInfo, &guestInfo)
if err != nil {
log.Log.Errorf("Cannot unmarshal GuestOSInfo response: %s", err.Error())
}

return guestInfo, err
}
Loading

0 comments on commit c5ae5d3

Please sign in to comment.