diff --git a/api/src/main/java/org/apache/cloudstack/agent/ApplianceAgentService.java b/api/src/main/java/org/apache/cloudstack/agent/ApplianceAgentService.java
new file mode 100644
index 000000000000..6195feae8009
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/agent/ApplianceAgentService.java
@@ -0,0 +1,24 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.agent;
+
+import org.apache.cloudstack.api.command.admin.agent.LivePatchCmd;
+
+public interface ApplianceAgentService {
+ String livePatch(LivePatchCmd cmd);
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/agent/LivePatchCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/agent/LivePatchCmd.java
new file mode 100644
index 000000000000..7302528da7a8
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/agent/LivePatchCmd.java
@@ -0,0 +1,96 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.api.command.admin.agent;
+
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.agent.ApplianceAgentService;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.SystemVmResponse;
+
+import com.cloud.user.Account;
+import com.google.common.base.Strings;
+
+@APICommand(name = LivePatchCmd.APINAME,
+ description = "Live Patches Appliance Agent",
+ responseObject = SuccessResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+ since = "4.13.0",
+ authorized = {RoleType.Admin})
+public class LivePatchCmd extends BaseCmd {
+ public static final String APINAME = "livePatch";
+
+ @Inject
+ ApplianceAgentService applianceAgentService;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, required = true, entityType = SystemVmResponse.class,
+ validations = {ApiArgValidator.PositiveNumber},
+ description = "The ID of the system VM appliance to live patch")
+ private Long id;
+
+ @Parameter(name = ApiConstants.TOKEN, type = CommandType.STRING,
+ description = "Ping token")
+ private String token;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return Account.ACCOUNT_ID_SYSTEM;
+ }
+
+ @Override
+ public void execute() {
+ String reply = applianceAgentService.livePatch(this);
+ SuccessResponse response = new SuccessResponse();
+ response.setDisplayText(reply);
+ response.setSuccess(!Strings.isNullOrEmpty(reply));
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+}
diff --git a/framework/appliance-agent/agent/agent.go b/framework/appliance-agent/agent/agent.go
new file mode 100644
index 000000000000..30bf5c05263c
--- /dev/null
+++ b/framework/appliance-agent/agent/agent.go
@@ -0,0 +1,34 @@
+// Virtual Appliance Agent
+package main
+
+import (
+ "context"
+ "log"
+ "net"
+
+ "google.golang.org/grpc"
+ pb "./virtualappliance"
+)
+
+const (
+ port = ":8200"
+)
+
+type server struct{}
+
+func (s *server) Ping(ctx context.Context, in *pb.PingRequest) (*pb.PingResponse, error) {
+ log.Printf("Received: %v", in.Message)
+ return &pb.PingResponse{Message: "Pong " + in.Message}, nil
+}
+
+func main() {
+ lis, err := net.Listen("tcp", port)
+ if err != nil {
+ log.Fatalf("failed to listen: %v", err)
+ }
+ s := grpc.NewServer()
+ pb.RegisterApplianceAgentServer(s, &server{})
+ if err := s.Serve(lis); err != nil {
+ log.Fatalf("failed to serve: %v", err)
+ }
+}
diff --git a/framework/appliance-agent/agent/testclient.go b/framework/appliance-agent/agent/testclient.go
new file mode 100644
index 000000000000..189ca374f7c2
--- /dev/null
+++ b/framework/appliance-agent/agent/testclient.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "time"
+
+ "google.golang.org/grpc"
+ pb "./virtualappliance"
+)
+
+const (
+ address = "localhost:50051"
+ defaultName = "client123"
+)
+
+func main() {
+ conn, err := grpc.Dial(address, grpc.WithInsecure())
+ if err != nil {
+ log.Fatalf("did not connect: %v", err)
+ }
+ defer conn.Close()
+ c := pb.NewApplianceAgentClient(conn)
+
+ name := defaultName
+ if len(os.Args) > 1 {
+ name = os.Args[1]
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+ r, err := c.Ping(ctx, &pb.PingRequest{Message: name})
+ if err != nil {
+ log.Fatalf("could not ping due to: %v", err)
+ }
+ log.Printf("Pinging: %s", r.Message)
+}
diff --git a/framework/appliance-agent/agent/virtualappliance/VirtualAppliance.pb.go b/framework/appliance-agent/agent/virtualappliance/VirtualAppliance.pb.go
new file mode 100644
index 000000000000..628e148327f4
--- /dev/null
+++ b/framework/appliance-agent/agent/virtualappliance/VirtualAppliance.pb.go
@@ -0,0 +1,206 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: VirtualAppliance.proto
+
+package virtualappliance
+
+import (
+ context "context"
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type PingRequest struct {
+ Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PingRequest) Reset() { *m = PingRequest{} }
+func (m *PingRequest) String() string { return proto.CompactTextString(m) }
+func (*PingRequest) ProtoMessage() {}
+func (*PingRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_1dcc0045f14d07fe, []int{0}
+}
+
+func (m *PingRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PingRequest.Unmarshal(m, b)
+}
+func (m *PingRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PingRequest.Marshal(b, m, deterministic)
+}
+func (m *PingRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PingRequest.Merge(m, src)
+}
+func (m *PingRequest) XXX_Size() int {
+ return xxx_messageInfo_PingRequest.Size(m)
+}
+func (m *PingRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_PingRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PingRequest proto.InternalMessageInfo
+
+func (m *PingRequest) GetMessage() string {
+ if m != nil {
+ return m.Message
+ }
+ return ""
+}
+
+type PingResponse struct {
+ Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PingResponse) Reset() { *m = PingResponse{} }
+func (m *PingResponse) String() string { return proto.CompactTextString(m) }
+func (*PingResponse) ProtoMessage() {}
+func (*PingResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_1dcc0045f14d07fe, []int{1}
+}
+
+func (m *PingResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PingResponse.Unmarshal(m, b)
+}
+func (m *PingResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PingResponse.Marshal(b, m, deterministic)
+}
+func (m *PingResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PingResponse.Merge(m, src)
+}
+func (m *PingResponse) XXX_Size() int {
+ return xxx_messageInfo_PingResponse.Size(m)
+}
+func (m *PingResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_PingResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PingResponse proto.InternalMessageInfo
+
+func (m *PingResponse) GetMessage() string {
+ if m != nil {
+ return m.Message
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*PingRequest)(nil), "virtualappliance.PingRequest")
+ proto.RegisterType((*PingResponse)(nil), "virtualappliance.PingResponse")
+}
+
+func init() { proto.RegisterFile("VirtualAppliance.proto", fileDescriptor_1dcc0045f14d07fe) }
+
+var fileDescriptor_1dcc0045f14d07fe = []byte{
+ // 184 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x0b, 0xcb, 0x2c, 0x2a,
+ 0x29, 0x4d, 0xcc, 0x71, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0xcc, 0x4b, 0x4e, 0xd5, 0x2b, 0x28, 0xca,
+ 0x2f, 0xc9, 0x17, 0x12, 0x28, 0x83, 0x88, 0x27, 0xc2, 0xc4, 0x95, 0xd4, 0xb9, 0xb8, 0x03, 0x32,
+ 0xf3, 0xd2, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x24, 0xb8, 0xd8, 0x73, 0x53, 0x8b,
+ 0x8b, 0x13, 0xd3, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x60, 0x5c, 0x25, 0x0d, 0x2e,
+ 0x1e, 0x88, 0xc2, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0xdc, 0x2a, 0x8d, 0x22, 0xb9, 0xf8, 0xe0,
+ 0xf6, 0x3a, 0xa6, 0xa7, 0xe6, 0x95, 0x08, 0xb9, 0x73, 0xb1, 0x80, 0xf4, 0x0a, 0xc9, 0xea, 0xa1,
+ 0xdb, 0xaf, 0x87, 0x64, 0xb9, 0x94, 0x1c, 0x2e, 0x69, 0x88, 0x95, 0x4a, 0x0c, 0x4e, 0xee, 0x5c,
+ 0xfa, 0xf9, 0x45, 0xe9, 0x7a, 0x89, 0x05, 0x89, 0xc9, 0x19, 0xa9, 0x7a, 0xc9, 0x39, 0xf9, 0xa5,
+ 0x29, 0xc5, 0x25, 0x89, 0xc9, 0xd9, 0x98, 0x1a, 0xc1, 0xfe, 0x4d, 0x2a, 0x4d, 0x73, 0x12, 0x42,
+ 0x0f, 0x8a, 0x00, 0xa7, 0x00, 0xc6, 0x24, 0x36, 0xb0, 0xbc, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff,
+ 0xa7, 0xe9, 0x61, 0x15, 0x29, 0x01, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// ApplianceAgentClient is the client API for ApplianceAgent service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type ApplianceAgentClient interface {
+ Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
+}
+
+type applianceAgentClient struct {
+ cc *grpc.ClientConn
+}
+
+func NewApplianceAgentClient(cc *grpc.ClientConn) ApplianceAgentClient {
+ return &applianceAgentClient{cc}
+}
+
+func (c *applianceAgentClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) {
+ out := new(PingResponse)
+ err := c.cc.Invoke(ctx, "/virtualappliance.ApplianceAgent/Ping", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// ApplianceAgentServer is the server API for ApplianceAgent service.
+type ApplianceAgentServer interface {
+ Ping(context.Context, *PingRequest) (*PingResponse, error)
+}
+
+// UnimplementedApplianceAgentServer can be embedded to have forward compatible implementations.
+type UnimplementedApplianceAgentServer struct {
+}
+
+func (*UnimplementedApplianceAgentServer) Ping(ctx context.Context, req *PingRequest) (*PingResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
+}
+
+func RegisterApplianceAgentServer(s *grpc.Server, srv ApplianceAgentServer) {
+ s.RegisterService(&_ApplianceAgent_serviceDesc, srv)
+}
+
+func _ApplianceAgent_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(PingRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ApplianceAgentServer).Ping(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/virtualappliance.ApplianceAgent/Ping",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ApplianceAgentServer).Ping(ctx, req.(*PingRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+var _ApplianceAgent_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "virtualappliance.ApplianceAgent",
+ HandlerType: (*ApplianceAgentServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "Ping",
+ Handler: _ApplianceAgent_Ping_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "VirtualAppliance.proto",
+}
diff --git a/framework/appliance-agent/gen.sh b/framework/appliance-agent/gen.sh
new file mode 100755
index 000000000000..9091141d8336
--- /dev/null
+++ b/framework/appliance-agent/gen.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -x
+protoc -I src/main/proto VirtualAppliance.proto --go_out=plugins=grpc:agent/virtualappliance
+(cd agent; go build agent.go; cp agent ../../../systemvm/debian/opt/cloud/bin/)
diff --git a/framework/appliance-agent/pom.xml b/framework/appliance-agent/pom.xml
new file mode 100644
index 000000000000..246fb3950594
--- /dev/null
+++ b/framework/appliance-agent/pom.xml
@@ -0,0 +1,102 @@
+
+
+ 4.0.0
+ cloud-framework-appliance-agent
+ Apache CloudStack Framework - Appliance Agent
+
+ org.apache.cloudstack
+ cloudstack-framework
+ 4.13.0.0-SNAPSHOT
+ ../pom.xml
+
+
+
+ org.apache.cloudstack
+ cloud-utils
+ ${project.version}
+
+
+ org.apache.cloudstack
+ cloud-framework-ca
+ ${project.version}
+
+
+ org.apache.cloudstack
+ cloud-api
+ ${project.version}
+
+
+ com.google.protobuf
+ protobuf-java
+ 3.7.1
+
+
+ com.google.protobuf
+ protobuf-java-util
+ 3.7.1
+
+
+ io.grpc
+ grpc-netty
+ 1.20.0
+ runtime
+
+
+ io.grpc
+ grpc-protobuf
+ 1.20.0
+
+
+ io.grpc
+ grpc-stub
+ 1.20.0
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.5.0.Final
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ 0.5.0
+
+ com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:1.20.0:exe:${os.detected.classifier}
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+
diff --git a/framework/appliance-agent/src/main/java/org/apache/cloudstack/virtualappliance/ApplianceAgentRpcClient.java b/framework/appliance-agent/src/main/java/org/apache/cloudstack/virtualappliance/ApplianceAgentRpcClient.java
new file mode 100644
index 000000000000..c80555646d65
--- /dev/null
+++ b/framework/appliance-agent/src/main/java/org/apache/cloudstack/virtualappliance/ApplianceAgentRpcClient.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.virtualappliance;
+
+import org.apache.cloudstack.virtualappliance.protobuf.ApplianceAgentGrpc;
+import org.apache.cloudstack.virtualappliance.protobuf.PingRequest;
+import org.apache.cloudstack.virtualappliance.protobuf.PingResponse;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+
+public class ApplianceAgentRpcClient {
+ ApplianceAgentGrpc.ApplianceAgentBlockingStub blockingStub;
+
+ public ApplianceAgentRpcClient() {
+ ManagedChannel managedChannel = ManagedChannelBuilder
+ .forAddress("localhost", 8200).usePlaintext().build();
+ blockingStub = ApplianceAgentGrpc.newBlockingStub(managedChannel);
+ }
+
+ public ApplianceAgentRpcClient(String host, Integer port) {
+ ManagedChannel managedChannel = ManagedChannelBuilder
+ .forAddress(host, port).usePlaintext().build();
+ blockingStub = ApplianceAgentGrpc.newBlockingStub(managedChannel);
+ }
+
+ public PingResponse ping(final PingRequest request) {
+ return blockingStub.ping(request);
+ }
+}
diff --git a/framework/appliance-agent/src/main/proto/VirtualAppliance.proto b/framework/appliance-agent/src/main/proto/VirtualAppliance.proto
new file mode 100644
index 000000000000..73751b469557
--- /dev/null
+++ b/framework/appliance-agent/src/main/proto/VirtualAppliance.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "org.apache.cloudstack.virtualappliance.protobuf";
+option java_outer_classname = "VirtualAppliancePB";
+
+package virtualappliance;
+
+service ApplianceAgent {
+ rpc Ping (PingRequest) returns (PingResponse) {}
+}
+
+message PingRequest {
+ string message = 1;
+}
+
+message PingResponse {
+ string message = 1;
+}
diff --git a/framework/appliance-agent/src/test/java/org/apache/cloudstack/virtualappliance/ApplianceAgentRpcClientTest.java b/framework/appliance-agent/src/test/java/org/apache/cloudstack/virtualappliance/ApplianceAgentRpcClientTest.java
new file mode 100644
index 000000000000..de14fd84dc3b
--- /dev/null
+++ b/framework/appliance-agent/src/test/java/org/apache/cloudstack/virtualappliance/ApplianceAgentRpcClientTest.java
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.virtualappliance;
+
+import org.apache.cloudstack.virtualappliance.protobuf.PingRequest;
+import org.apache.cloudstack.virtualappliance.protobuf.PingResponse;
+import org.junit.Assert;
+
+public class ApplianceAgentRpcClientTest {
+
+ //@Test
+ public void ping() {
+ ApplianceAgentRpcClient client = new ApplianceAgentRpcClient("172.20.20.12", 8200);
+ String message = "GRPC based Agent Works!";
+ PingRequest request = PingRequest.newBuilder()
+ .setMessage(message)
+ .build();
+ PingResponse response = client.ping(request);
+ Assert.assertEquals("Pong " + message, response.getMessage());
+ }
+}
diff --git a/framework/pom.xml b/framework/pom.xml
index c42bb3c24c75..8c64500fb531 100644
--- a/framework/pom.xml
+++ b/framework/pom.xml
@@ -45,6 +45,7 @@
agent-lb
+ appliance-agent
ca
cluster
config
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java
index da9f1f3b3d1a..f3839f0fc761 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java
@@ -118,9 +118,14 @@ public Answer execute(final StartCommand command, final LibvirtComputingResource
// try to patch and SSH into the systemvm for up to 5 minutes
for (int count = 0; count < 10; count++) {
// wait and try passCmdLine for 30 seconds at most for CLOUDSTACK-2823
- libvirtComputingResource.passCmdLine(vmName, vmSpec.getBootArgs());
- // check router is up?
- final VirtualRoutingResource virtRouterResource = libvirtComputingResource.getVirtRouterResource();
+ if (libvirtComputingResource.passCmdLine(vmName, vmSpec.getBootArgs())) {
+ break;
+ }
+ }
+
+ final VirtualRoutingResource virtRouterResource = libvirtComputingResource.getVirtRouterResource();
+ // check if the router is up?
+ for (int count = 0; count < 60; count++) {
final boolean result = virtRouterResource.connect(controlIp, 1, 5000);
if (result) {
break;
diff --git a/pom.xml b/pom.xml
index 44f0cbc1e2a0..e559f5437f8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -760,7 +760,7 @@
${project.basedir}
**\/*.java
- **\/deps\/,**\/test\/,**\/target\/,**\/bin\/,**\/*.xml,**\/*.ini,**\/*.sh,**\/*.bat,**\/XmlToHtmlConverter*,**\/generated-sources\/**\/*
+ **\/deps\/,**\/test\/,**\/target\/,**\/bin\/,**\/*.xml,**\/*.ini,**\/*.sh,**\/*.bat,**\/XmlToHtmlConverter*,**\/generated-sources\/**\/*,**\/protobuf\/
diff --git a/server/pom.xml b/server/pom.xml
index c0ee1197dcb1..ef14b5186893 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -64,6 +64,11 @@
cloud-framework-ca
${project.version}
+
+ org.apache.cloudstack
+ cloud-framework-appliance-agent
+ ${project.version}
+
org.apache.cloudstack
cloud-framework-jobs
diff --git a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
index 68c12333c5e3..238171f4db3c 100644
--- a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
@@ -620,7 +620,7 @@ public void updateKeyPairs() {
s_logger.error("Cannot read the private key file", e);
throw new CloudRuntimeException("Cannot read the private key file");
}
- String privateKey = new String(arr1).trim();
+ String privateKey = new String(arr1).trim() + "\n";
byte[] arr2 = new byte[4094]; // configuration table column value size
try (DataInputStream dis = new DataInputStream(new FileInputStream(pubkeyfile))) {
dis.readFully(arr2);
@@ -630,7 +630,7 @@ public void updateKeyPairs() {
s_logger.warn("Cannot read the public key file", e);
throw new CloudRuntimeException("Cannot read the public key file");
}
- String publicKey = new String(arr2).trim();
+ String publicKey = new String(arr2).trim() + "\n";
final String insertSql1 =
"INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) " +
diff --git a/server/src/main/java/org/apache/cloudstack/agent/ApplianceAgentManagerImpl.java b/server/src/main/java/org/apache/cloudstack/agent/ApplianceAgentManagerImpl.java
new file mode 100644
index 000000000000..e83da3cafe02
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/agent/ApplianceAgentManagerImpl.java
@@ -0,0 +1,80 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.agent;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.api.command.admin.agent.LivePatchCmd;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.virtualappliance.ApplianceAgentRpcClient;
+import org.apache.cloudstack.virtualappliance.protobuf.PingRequest;
+import org.apache.cloudstack.virtualappliance.protobuf.PingResponse;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.network.Networks;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.google.common.base.Strings;
+
+public class ApplianceAgentManagerImpl extends ManagerBase implements PluggableService, ApplianceAgentService {
+
+ @Inject
+ VMInstanceDao vmInstanceDao;
+
+ @Inject
+ private NetworkOrchestrationService networkManager;
+
+ @Override
+ public List> getCommands() {
+ return Arrays.asList(LivePatchCmd.class);
+ }
+
+ @Override
+ public String livePatch(LivePatchCmd cmd) {
+ final Long vmId = cmd.getId();
+ final String message = cmd.getToken();
+ final VMInstanceVO vmInstance = vmInstanceDao.findByIdTypes(vmId, VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.DomainRouter, VirtualMachine.Type.SecondaryStorageVm);
+
+ if (vmInstance == null) {
+ throw new InvalidParameterValueException("Unable to find a system vm with the requested ID)");
+ }
+
+ final Map accessDetails = networkManager.getSystemVMAccessDetails(vmInstance);
+ final String applianceManagementAddress = accessDetails.get(Networks.TrafficType.Public.name());
+
+ if (Strings.isNullOrEmpty(applianceManagementAddress)) {
+ throw new CloudRuntimeException("Unable to set system vm management IP for system vm");
+ }
+
+ // TODO: exception handling, checks etc.
+ ApplianceAgentRpcClient client = new ApplianceAgentRpcClient(applianceManagementAddress, 8200);
+ PingRequest request = PingRequest.newBuilder()
+ .setMessage(message)
+ .build();
+ PingResponse response = client.ping(request);
+ return response.getMessage();
+ }
+}
diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
index 2f67c4248d35..b2ce0614d9d0 100644
--- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
@@ -300,4 +300,7 @@
+
+
+
diff --git a/systemvm/debian/etc/iptables/iptables-router b/systemvm/debian/etc/iptables/iptables-router
index 9851ee7dbd96..1594f5fa3f72 100644
--- a/systemvm/debian/etc/iptables/iptables-router
+++ b/systemvm/debian/etc/iptables/iptables-router
@@ -38,6 +38,7 @@ COMMIT
-A INPUT -i eth0 -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -i eth1 -p tcp -m tcp -m state --state NEW,ESTABLISHED --dport 3922 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp -m state --state NEW --dport 80 -j ACCEPT
+-A INPUT -p tcp -m tcp -m state --state NEW,ESTABLISHED --dport 8200 -j ACCEPT
-A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i eth2 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i eth0 -o eth0 -m state --state NEW -j ACCEPT
diff --git a/systemvm/debian/etc/iptables/iptables-secstorage b/systemvm/debian/etc/iptables/iptables-secstorage
index 019aaa0bfb40..0d53f48c3b3d 100755
--- a/systemvm/debian/etc/iptables/iptables-secstorage
+++ b/systemvm/debian/etc/iptables/iptables-secstorage
@@ -33,4 +33,5 @@ COMMIT
-A INPUT -p icmp --icmp-type 13 -j DROP
-A INPUT -p icmp -j ACCEPT
-A INPUT -i eth0 -p tcp -m state --state NEW --dport 3922 -j ACCEPT
+-A INPUT -p tcp -m tcp -m state --state NEW,ESTABLISHED --dport 8200 -j ACCEPT
COMMIT
diff --git a/systemvm/debian/etc/iptables/iptables-vpcrouter b/systemvm/debian/etc/iptables/iptables-vpcrouter
index e6237c5a1cd8..4d8716277952 100644
--- a/systemvm/debian/etc/iptables/iptables-vpcrouter
+++ b/systemvm/debian/etc/iptables/iptables-vpcrouter
@@ -29,6 +29,7 @@ COMMIT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp -m state --state NEW,ESTABLISHED --dport 3922 -j ACCEPT
+-A INPUT -p tcp -m tcp -m state --state NEW,ESTABLISHED --dport 8200 -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
COMMIT
diff --git a/systemvm/debian/opt/cloud/bin/setup/router.sh b/systemvm/debian/opt/cloud/bin/setup/router.sh
index 6bb3b6f21461..e1fe8cd9646f 100755
--- a/systemvm/debian/opt/cloud/bin/setup/router.sh
+++ b/systemvm/debian/opt/cloud/bin/setup/router.sh
@@ -96,7 +96,9 @@ setup_router() {
sed -i "s/-A INPUT -i eth0 -p tcp -m tcp --dport 53 -j ACCEPT/-A INPUT -i eth0 -p tcp -m tcp --dport 53 -s $DHCP_RANGE\/$CIDR_SIZE -j ACCEPT/g" /etc/iptables/rules.v4
# Setup hourly logrotate
- mv -n /etc/cron.daily/logrotate /etc/cron.hourly 2>&1
+ if [ -f /etc/cron.daily/logrotate ]; then
+ mv -n /etc/cron.daily/logrotate /etc/cron.hourly 2>&1
+ fi
}
routing_svcs
diff --git a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh
index af111adc6ba7..9e6cdb7b74b6 100755
--- a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh
+++ b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh
@@ -108,8 +108,10 @@ EOF
echo 0 > /var/cache/cloud/dnsmasq_managed_lease
fi
- #setup hourly logrotate
- mv -n /etc/cron.daily/logrotate /etc/cron.hourly 2>&1
+ # Setup hourly logrotate
+ if [ -f /etc/cron.daily/logrotate ]; then
+ mv -n /etc/cron.daily/logrotate /etc/cron.hourly 2>&1
+ fi
}
routing_svcs
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index 1b2a979d443b..99ecb6f46eef 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -192,6 +192,7 @@
'Sioc' : 'Sioc',
'Diagnostics': 'Diagnostics',
'Management': 'Management',
+ 'livePatch': 'Router',
}