Skip to content

Commit 34030be

Browse files
Fix XenServer Security Groups 'vmops' script (#3197)
* Fix XenServer Security Groups 'vmops' script - fix tokens = line.split(':') to tokens = line.split(';') - fix expected tokens size from 5 to 4 - enhance logs - remove unused vmops script. The XCP patch points to the vmops script on the parent folder [1]. Thus, all XenServer versions are considering the vmops script located at [2]. - fix UI ipv4/ipv6 cidr validator to allow a list of cidirs. Fixing issue: #3192 Security Group rules not applied at all for XenServer 6.5 / Advanced Zone #3192 * Update security group rules after VM migration Add security group rules on target host Cause: vmops script expected secondary IPs as "0;" but received "0:" Remove security group network rules on source host. Cause: destroy_network_rules_for_vm function on vmops script was not called when migrating VM * Add unit tests and address reviewers
1 parent 709845f commit 34030be

7 files changed

Lines changed: 146 additions & 1471 deletions

File tree

core/src/main/java/com/cloud/agent/api/SecurityGroupRulesCmd.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public String getSecIpsString() {
173173
final StringBuilder sb = new StringBuilder();
174174
final List<String> ips = getSecIps();
175175
if (ips == null) {
176-
sb.append("0:");
176+
sb.append("0").append(RULE_COMMAND_SEPARATOR);
177177
} else {
178178
for (final String ip : ips) {
179179
sb.append(ip).append(RULE_COMMAND_SEPARATOR);

plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixMigrateCommandWrapper.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
import java.util.Set;
2323

24+
import org.apache.commons.lang.BooleanUtils;
2425
import org.apache.log4j.Logger;
26+
import org.apache.xmlrpc.XmlRpcException;
2527

2628
import com.cloud.agent.api.Answer;
2729
import com.cloud.agent.api.MigrateAnswer;
@@ -32,11 +34,13 @@
3234
import com.xensource.xenapi.Connection;
3335
import com.xensource.xenapi.Host;
3436
import com.xensource.xenapi.Types;
37+
import com.xensource.xenapi.Types.BadServerResponse;
38+
import com.xensource.xenapi.Types.XenAPIException;
3539
import com.xensource.xenapi.VBD;
3640
import com.xensource.xenapi.VM;
3741

3842
@ResourceWrapper(handles = MigrateCommand.class)
39-
public final class CitrixMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, CitrixResourceBase> {
43+
public class CitrixMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, CitrixResourceBase> {
4044

4145
private static final Logger s_logger = Logger.getLogger(CitrixMigrateCommandWrapper.class);
4246

@@ -81,6 +85,8 @@ public Answer execute(final MigrateCommand command, final CitrixResourceBase cit
8185
}
8286
citrixResourceBase.migrateVM(conn, dsthost, vm, vmName);
8387
vm.setAffinity(conn, dsthost);
88+
89+
destroyMigratedVmNetworkRulesOnSourceHost(command, citrixResourceBase, conn, dsthost);
8490
}
8591

8692
// The iso can be attached to vm only once the vm is (present in the host) migrated.
@@ -95,4 +101,19 @@ public Answer execute(final MigrateCommand command, final CitrixResourceBase cit
95101
return new MigrateAnswer(command, false, e.getMessage(), null);
96102
}
97103
}
104+
105+
/**
106+
* On networks with security group, it removes the network rules related to the migrated VM from the source host.
107+
*/
108+
protected void destroyMigratedVmNetworkRulesOnSourceHost(final MigrateCommand command, final CitrixResourceBase citrixResourceBase, final Connection conn, Host dsthost)
109+
throws BadServerResponse, XenAPIException, XmlRpcException {
110+
if (citrixResourceBase.canBridgeFirewall()) {
111+
final String result = citrixResourceBase.callHostPlugin(conn, "vmops", "destroy_network_rules_for_vm", "vmName", command.getVmName());
112+
if (BooleanUtils.toBoolean(result)) {
113+
s_logger.debug(String.format("Removed network rules from source host [%s] for migrated vm [%s]", dsthost.getHostname(conn), command.getVmName()));
114+
} else {
115+
s_logger.warn(String.format("Failed to remove network rules from source host [%s] for migrated vm [%s]", dsthost.getHostname(conn), command.getVmName()));
116+
}
117+
}
118+
}
98119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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.hypervisor.xenserver.resource.wrapper.xenbase;
20+
21+
import org.apache.xmlrpc.XmlRpcException;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
import org.mockito.Mock;
25+
import org.mockito.Mockito;
26+
import org.mockito.Spy;
27+
import org.mockito.runners.MockitoJUnitRunner;
28+
29+
import com.cloud.agent.api.MigrateCommand;
30+
import com.cloud.agent.api.to.VirtualMachineTO;
31+
import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase;
32+
import com.xensource.xenapi.Connection;
33+
import com.xensource.xenapi.Host;
34+
import com.xensource.xenapi.Types.BadServerResponse;
35+
import com.xensource.xenapi.Types.XenAPIException;
36+
37+
@RunWith(MockitoJUnitRunner.class)
38+
public class CitrixMigrateCommandWrapperTest {
39+
40+
@Mock
41+
private CitrixResourceBase citrixResourceBase;
42+
@Spy
43+
private CitrixMigrateCommandWrapper citrixMigrateCommandWrapper;
44+
45+
@Test
46+
public void destroyNetworkRulesOnSourceHostForMigratedVmTestSupportSecurityGroup() throws BadServerResponse, XenAPIException, XmlRpcException {
47+
configureExecuteAndVerifyDestroyNetworkRulesOnSourceHostForMigratedVmTest(true, 1);
48+
}
49+
50+
@Test
51+
public void destroyNetworkRulesOnSourceHostForMigratedVmTestDoesNotSupportSecurityGroup() throws BadServerResponse, XenAPIException, XmlRpcException {
52+
configureExecuteAndVerifyDestroyNetworkRulesOnSourceHostForMigratedVmTest(false, 0);
53+
}
54+
55+
private void configureExecuteAndVerifyDestroyNetworkRulesOnSourceHostForMigratedVmTest(boolean canBridgeFirewall, int timesCallHostPlugin)
56+
throws BadServerResponse, XenAPIException, XmlRpcException {
57+
58+
final MigrateCommand command = new MigrateCommand("migratedVmName", "destHostIp", false, Mockito.mock(VirtualMachineTO.class), true);
59+
Connection conn = Mockito.mock(Connection.class);
60+
Host dsthost = Mockito.mock(Host.class);
61+
Mockito.doReturn("hostname").when(dsthost).getHostname(conn);
62+
63+
Mockito.doReturn(canBridgeFirewall).when(citrixResourceBase).canBridgeFirewall();
64+
65+
citrixMigrateCommandWrapper.destroyMigratedVmNetworkRulesOnSourceHost(command, citrixResourceBase, conn, dsthost);
66+
67+
Mockito.verify(citrixResourceBase, Mockito.times(timesCallHostPlugin)).callHostPlugin(conn, "vmops", "destroy_network_rules_for_vm", "vmName", "migratedVmName");
68+
}
69+
70+
}

scripts/vm/hypervisor/xenserver/vmops

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ def default_network_rules(session, args):
824824
logging.debug(" failed to add vm " + vm_ip + " ip to set ")
825825
return 'false'
826826

827-
#add secodnary nic ips to ipset
827+
#add secondary nic ips to ipset
828828
secIpSet = "1"
829829
ips = sec_ips.split(';')
830830
ips.pop()
@@ -1419,12 +1419,22 @@ def network_rules(session, args):
14191419
reason = 'domid_change'
14201420

14211421
rules = args.pop('rules')
1422+
logging.debug("Network rules compressed in base64 [%s]." % rules)
1423+
14221424
if deflated.lower() == 'true':
14231425
rules = inflate_rules (rules)
14241426
keyword = '--' + get_ipset_keyword()
1427+
1428+
#Split spaces into lines. Example of rule [I:tcp;22;22;0.0.0.0/22,NEXT I:tcp;8001;8010;192.168.100.0/24,NEXT E:tcp;22;22;0.0.0.0/0,NEXT ]
1429+
#After split:
1430+
# I:tcp;22;22;0.0.0.0/22,NEXT
1431+
# I:tcp;8001;8010;192.168.100.0/24,NEXT
1432+
# E:tcp;22;22;0.0.0.0/0,NEXT
14251433
lines = rules.split(' ')
14261434

1427-
logging.debug("Programming network rules for vm %s seqno=%s numrules=%s signature=%s guestIp=%s,"\
1435+
logging.info("Network rules to be processed [%s]." % rules)
1436+
1437+
logging.info("Programming network rules for vm %s seqno=%s numrules=%s signature=%s guestIp=%s,"\
14281438
" update iptables, reason=%s" % (vm_name, seqno, len(lines), signature, vm_ip, reason))
14291439

14301440
# Flush iptables rules to clear ipset references and before re-applying iptable rules
@@ -1438,14 +1448,24 @@ def network_rules(session, args):
14381448
cmds = []
14391449
egressrules = 0
14401450
for line in lines:
1441-
tokens = line.split(':')
1442-
if len(tokens) != 5:
1443-
continue
1444-
token_type = tokens[0]
1445-
protocol = tokens[1]
1446-
start = tokens[2]
1447-
end = tokens[3]
1448-
cidrs = tokens.pop().split(",")
1451+
logging.debug("Processing rule [%s]." % line)
1452+
1453+
#Example of rule: [I:tcp;12;34;1.2.3.4/24,NEXT] -> tokens: ['I:tcp', '12', '34', '1.2.3.4/24,NEXT'].
1454+
tokens = line.split(';')
1455+
logging.debug("Tokens %s." % tokens)
1456+
1457+
tokens_size = len(tokens)
1458+
1459+
expected_tokens_size = 4
1460+
if tokens_size != expected_tokens_size:
1461+
logging.warning("Network rule tokens size [%s] is different from the expected size [%s], ignoring rule %s" % (str(tokens_size), str(expected_tokens_size), tokens))
1462+
continue
1463+
1464+
token_type, protocol = tokens[0].split(':')
1465+
start = tokens[1]
1466+
end = tokens[2]
1467+
cidrs = tokens[3].split(",")
1468+
# Remove placeholder 'NEXT' from the cidrs array
14491469
cidrs.pop()
14501470
allow_any = False
14511471

@@ -1456,17 +1476,22 @@ def network_rules(session, args):
14561476
action = "RETURN"
14571477
direction = "dst"
14581478
egressrules = egressrules + 1
1479+
logging.debug("Ipset chaing [%s], VM chain [%s], VM name [%s], Action [%s], Direction [%s], egress rules [%s]" % (ipset_chain, vmchain, vm_name, action, direction, egressrules))
14591480
else:
14601481
#ipset chain name
14611482
ipset_chain = ingress_chain_name_ipset(vm_name)
14621483
vmchain = chain_name(vm_name)
14631484
action = "ACCEPT"
14641485
direction = "src"
1465-
if '0.0.0.0/0' in cidrs:
1486+
logging.debug("Ipset chaing [%s], VM [%s], Action [%s], Direction [%s], egress rules [%s]" % (ipset_chain, vm_name, action, direction, egressrules))
1487+
if '0.0.0.0/0' in cidrs:
14661488
i = cidrs.index('0.0.0.0/0')
14671489
del cidrs[i]
14681490
allow_any = True
1491+
14691492
port_range = start + ":" + end
1493+
logging.debug("port range [%s]" % port_range)
1494+
14701495
if cidrs:
14711496
#create seperate ipset name
14721497
ipsetname = ipset_chain + "" + protocol[0:1] + "" + start + "_" + end
@@ -1500,6 +1525,7 @@ def network_rules(session, args):
15001525
logging.debug(iptables)
15011526

15021527
for cmd in cmds:
1528+
logging.debug("Executing command: [%s]." % str(cmd))
15031529
util.pread2(cmd)
15041530

15051531
vmchain = chain_name(vm_name)
@@ -1517,7 +1543,7 @@ def network_rules(session, args):
15171543

15181544
return 'true'
15191545
except:
1520-
logging.debug("Failed to network rule !")
1546+
logging.exception("Failed to network rule!")
15211547

15221548
if __name__ == "__main__":
15231549
XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi,

0 commit comments

Comments
 (0)