Skip to content

Commit 9280980

Browse files
committed
KVM: Rolling maintenance
1 parent 96d98de commit 9280980

29 files changed

Lines changed: 1136 additions & 3 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/python
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing,
11+
# software distributed under the License is distributed on an
12+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
# KIND, either express or implied. See the License for the
14+
# specific language governing permissions and limitations
15+
# under the License.
16+
17+
from subprocess import *
18+
import sys
19+
import time
20+
import logging
21+
22+
LOG_FILE='/var/log/cloudstack/agent/rolling-maintenance.log'
23+
24+
logging.basicConfig(filename=LOG_FILE,
25+
filemode='a',
26+
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
27+
datefmt='%H:%M:%S',
28+
level=logging.INFO)
29+
logger = logging.getLogger('rolling-maintenance')
30+
31+
def execute_script(stage, script):
32+
logger.info("Executing script: %s for stage: %s" % (script, stage))
33+
34+
try:
35+
command = '. ' + script
36+
pout = Popen(command, shell=True, stdout=PIPE)
37+
exitStatus = pout.wait()
38+
output = pout.communicate()[0]
39+
40+
if exitStatus == 0:
41+
logger.info("Successful execution of %s" % script)
42+
return {"success": "True", "message": output.strip()}
43+
else:
44+
logger.info("Script execution failed: %s" % script)
45+
return {"success": "False", "message": output.strip()}
46+
except Exception as e:
47+
logger.error("Error in script: %s for stage: %s" % (script, e))
48+
sys.exit(1)
49+
50+
if __name__ == '__main__':
51+
if len(sys.argv) != 2:
52+
logger.error("Wrong number of parameters received, STAGE,SCRIPT expected")
53+
sys.exit(0)
54+
55+
args = sys.argv[1]
56+
params = args.split(',')
57+
stage = params[0]
58+
script = params[1]
59+
logger.info("Received parameters: stage: %s and script: %s" % (stage, script))
60+
61+
execute_script(stage, script)
62+
logger.info("Finished executing stage: %s (file: %s)" % (stage, script))

agent/conf/agent.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ domr.scripts.dir=scripts/network/domr/kvm
115115
# set the hypervisor type, values are: kvm, lxc
116116
hypervisor.type=kvm
117117

118+
# set the rolling maintenance hook scripts directory
119+
#rolling.maintenance.hooks.dir=/etc/cloudstack/agent/hooks.d
120+
121+
# disable the rolling maintenance service execution
122+
#rolling.maintenance.service.executor.disabled=true
123+
118124
# set the hypervisor URI. Usually there is no need for changing this
119125
# For KVM: qemu:///system
120126
# For LXC: lxc:///
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.resource;
21+
22+
import com.cloud.utils.Pair;
23+
import com.cloud.utils.exception.CloudRuntimeException;
24+
import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd;
25+
26+
public interface RollingMaintenanceService {
27+
28+
enum Stage {
29+
PreFlight, PreMaintenance, Maintenance, PostMaintenance;
30+
31+
public Stage next() {
32+
switch (this) {
33+
case PreFlight:
34+
return PreMaintenance;
35+
case PreMaintenance:
36+
return Maintenance;
37+
case Maintenance:
38+
return PostMaintenance;
39+
case PostMaintenance:
40+
return null;
41+
}
42+
throw new CloudRuntimeException("Unexpected stage: " + this);
43+
}
44+
}
45+
46+
Pair<Boolean, String> startRollingMaintenance(StartRollingMaintenanceCmd cmd);
47+
}

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ public class ApiConstants {
245245
public static final String PASSWORD_ENABLED = "passwordenabled";
246246
public static final String SSHKEY_ENABLED = "sshkeyenabled";
247247
public static final String PATH = "path";
248+
public static final String PAYLOAD = "payload";
248249
public static final String POD_ID = "podid";
249250
public static final String POD_NAME = "podname";
250251
public static final String POD_IDS = "podids";
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.api.command.admin.resource;
19+
20+
import com.cloud.exception.ConcurrentOperationException;
21+
import com.cloud.exception.InsufficientCapacityException;
22+
import com.cloud.exception.NetworkRuleConflictException;
23+
import com.cloud.exception.ResourceAllocationException;
24+
import com.cloud.exception.ResourceUnavailableException;
25+
import com.cloud.resource.RollingMaintenanceService;
26+
import com.cloud.utils.Pair;
27+
import org.apache.cloudstack.acl.RoleType;
28+
import org.apache.cloudstack.api.APICommand;
29+
import org.apache.cloudstack.api.ApiConstants;
30+
import org.apache.cloudstack.api.BaseCmd;
31+
import org.apache.cloudstack.api.Parameter;
32+
import org.apache.cloudstack.api.ServerApiException;
33+
import org.apache.cloudstack.api.response.ClusterResponse;
34+
import org.apache.cloudstack.api.response.HostResponse;
35+
import org.apache.cloudstack.api.response.PodResponse;
36+
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
37+
import org.apache.cloudstack.api.response.ZoneResponse;
38+
import org.apache.cloudstack.context.CallContext;
39+
import org.apache.log4j.Logger;
40+
41+
import javax.inject.Inject;
42+
43+
@APICommand(name = StartRollingMaintenanceCmd.APINAME, description = "Start rolling maintenance",
44+
responseObject = RollingMaintenanceResponse.class,
45+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
46+
authorized = {RoleType.Admin})
47+
public class StartRollingMaintenanceCmd extends BaseCmd {
48+
49+
@Inject
50+
RollingMaintenanceService rollingMaintenanceService;
51+
52+
public static final Logger s_logger = Logger.getLogger(StartRollingMaintenanceCmd.class.getName());
53+
54+
public static final String APINAME = "startRollingMaintenance";
55+
56+
/////////////////////////////////////////////////////
57+
//////////////// API parameters /////////////////////
58+
/////////////////////////////////////////////////////
59+
60+
@Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID,
61+
entityType = PodResponse.class, description = "the ID of the pod to start maintenance on")
62+
private Long podId;
63+
64+
@Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID,
65+
entityType = ClusterResponse.class, description = "the ID of the cluster to start maintenance on")
66+
private Long clusterId;
67+
68+
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID,
69+
entityType = ZoneResponse.class, description = "the ID of the zone to start maintenance on")
70+
private Long zoneId;
71+
72+
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID,
73+
entityType = HostResponse.class, description = "the ID of the host to start maintenance on")
74+
private Long hostId;
75+
76+
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN,
77+
description = "if rolling mechanism should continue in case of an error")
78+
private Boolean forced;
79+
80+
@Parameter(name = ApiConstants.PAYLOAD, type = CommandType.STRING,
81+
description = "the command to execute while hosts are on maintenance")
82+
private String payload;
83+
84+
@Parameter(name = ApiConstants.TIMEOUT, type = CommandType.INTEGER,
85+
description = "optional operation timeout (in seconds) that overrides the global timeout setting")
86+
private Integer timeout;
87+
88+
/////////////////////////////////////////////////////
89+
/////////////////// Accessors ///////////////////////
90+
/////////////////////////////////////////////////////
91+
92+
public Long getPodId() {
93+
return podId;
94+
}
95+
96+
public Long getClusterId() {
97+
return clusterId;
98+
}
99+
100+
public Long getZoneId() {
101+
return zoneId;
102+
}
103+
104+
public Long getHostId() {
105+
return hostId;
106+
}
107+
108+
public Boolean getForced() {
109+
return forced != null && forced;
110+
}
111+
112+
public String getPayload() {
113+
return payload;
114+
}
115+
116+
public Integer getTimeout() {
117+
return timeout;
118+
}
119+
120+
/////////////////////////////////////////////////////
121+
/////////////// API Implementation///////////////////
122+
/////////////////////////////////////////////////////
123+
124+
@Override
125+
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
126+
Pair<Boolean, String> result = rollingMaintenanceService.startRollingMaintenance(this);
127+
RollingMaintenanceResponse response = new RollingMaintenanceResponse(result.first(), result.second());
128+
response.setResponseName(getCommandName());
129+
response.setObjectName("rollingmaintenance");
130+
this.setResponseObject(response);
131+
}
132+
133+
@Override
134+
public String getCommandName() {
135+
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
136+
}
137+
138+
@Override
139+
public long getEntityOwnerId() {
140+
return CallContext.current().getCallingAccountId();
141+
}
142+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.response;
18+
19+
import com.cloud.serializer.Param;
20+
import com.google.gson.annotations.SerializedName;
21+
import org.apache.cloudstack.api.BaseResponse;
22+
23+
public class RollingMaintenanceResponse extends BaseResponse {
24+
25+
@SerializedName("success")
26+
@Param(description = "indicates if the rolling maintenance operation was successful")
27+
private Boolean success;
28+
29+
@SerializedName("details")
30+
@Param(description = "in case of failure, details are displayed")
31+
private String details;
32+
33+
public RollingMaintenanceResponse(Boolean success, String details) {
34+
this.success = success;
35+
this.details = details;
36+
}
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
package com.cloud.agent.api;
20+
21+
public class RollingMaintenanceAnswer extends Answer {
22+
23+
private boolean finished;
24+
25+
public RollingMaintenanceAnswer(Command command, boolean success, String details, boolean finished) {
26+
super(command, success, details);
27+
this.finished = finished;
28+
}
29+
30+
public boolean isFinished() {
31+
return finished;
32+
}
33+
}

0 commit comments

Comments
 (0)