Andreas Papazois
Committed by Gerrit Code Review

[GEANT] Remove configuration from device interfaces.

Change-Id: Ie382a645c2ec8677ccd9ba6ed4b881e38686c1b9
...@@ -33,14 +33,14 @@ import org.onosproject.net.driver.DriverService; ...@@ -33,14 +33,14 @@ import org.onosproject.net.driver.DriverService;
33 public class DeviceInterfaceAddCommand extends AbstractShellCommand { 33 public class DeviceInterfaceAddCommand extends AbstractShellCommand {
34 34
35 private static final String CONFIG_VLAN_SUCCESS = 35 private static final String CONFIG_VLAN_SUCCESS =
36 - "VLAN %s set on device %s interface %s."; 36 + "VLAN %s added on device %s interface %s.";
37 private static final String CONFIG_VLAN_FAILURE = 37 private static final String CONFIG_VLAN_FAILURE =
38 - "Failed to set VLAN %s on device %s interface %s."; 38 + "Failed to add VLAN %s on device %s interface %s.";
39 39
40 private static final String CONFIG_TRUNK_SUCCESS = 40 private static final String CONFIG_TRUNK_SUCCESS =
41 - "Trunk mode set for VLAN %s on device %s interface %s."; 41 + "Trunk mode added for VLAN %s on device %s interface %s.";
42 private static final String CONFIG_TRUNK_FAILURE = 42 private static final String CONFIG_TRUNK_FAILURE =
43 - "Failed to set trunk mode for VLAN %s on device %s interface %s."; 43 + "Failed to add trunk mode for VLAN %s on device %s interface %s.";
44 44
45 @Argument(index = 0, name = "uri", description = "Device ID", 45 @Argument(index = 0, name = "uri", description = "Device ID",
46 required = true, multiValued = false) 46 required = true, multiValued = false)
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cli.net;
17 +
18 +import org.apache.karaf.shell.commands.Argument;
19 +import org.apache.karaf.shell.commands.Command;
20 +import org.apache.karaf.shell.commands.Option;
21 +import org.onlab.packet.VlanId;
22 +import org.onosproject.cli.AbstractShellCommand;
23 +import org.onosproject.net.DeviceId;
24 +import org.onosproject.net.behaviour.InterfaceConfig;
25 +import org.onosproject.net.driver.DriverHandler;
26 +import org.onosproject.net.driver.DriverService;
27 +
28 +/**
29 + * Removes configured interface from a device.
30 + */
31 +@Command(scope = "onos", name = "device-remove-interface",
32 + description = "Removes an interface configuration from a device")
33 +public class DeviceInterfaceRemoveCommand extends AbstractShellCommand {
34 +
35 + private static final String REMOVE_VLAN_SUCCESS =
36 + "VLAN %s removed from device %s interface %s.";
37 + private static final String REMOVE_VLAN_FAILURE =
38 + "Failed to remove VLAN %s from device %s interface %s.";
39 +
40 + private static final String REMOVE_TRUNK_SUCCESS =
41 + "Trunk mode removed for VLAN %s on device %s interface %s.";
42 + private static final String REMOVE_TRUNK_FAILURE =
43 + "Failed to remove trunk mode for VLAN %s on device %s interface %s.";
44 +
45 + @Argument(index = 0, name = "uri", description = "Device ID",
46 + required = true, multiValued = false)
47 + private String uri = null;
48 +
49 + @Argument(index = 1, name = "interface",
50 + description = "Interface name",
51 + required = true, multiValued = false)
52 + private String portName = null;
53 +
54 + @Argument(index = 2, name = "vlan",
55 + description = "VLAN ID",
56 + required = true, multiValued = false)
57 + private String vlanString = null;
58 +
59 + @Option(name = "-t", aliases = "--trunk",
60 + description = "Remove trunk mode for VLAN",
61 + required = false, multiValued = false)
62 + private boolean trunkMode = false;
63 +
64 + @Override
65 + protected void execute() {
66 + DriverService service = get(DriverService.class);
67 + DeviceId deviceId = DeviceId.deviceId(uri);
68 + DriverHandler h = service.createHandler(deviceId);
69 + InterfaceConfig interfaceConfig = h.behaviour(InterfaceConfig.class);
70 +
71 + VlanId vlanId = VlanId.vlanId(Short.parseShort(vlanString));
72 +
73 + if (trunkMode) {
74 + // Trunk mode for VLAN to be removed.
75 + if (interfaceConfig.removeTrunkInterface(deviceId, portName, vlanId)) {
76 + print(REMOVE_TRUNK_SUCCESS, vlanId, deviceId, portName);
77 + } else {
78 + print(REMOVE_TRUNK_FAILURE, vlanId, deviceId, portName);
79 + }
80 + return;
81 + }
82 +
83 + // Interface to be removed from VLAN.
84 + if (interfaceConfig.removeInterfaceFromVlan(deviceId, portName, vlanId)) {
85 + print(REMOVE_VLAN_SUCCESS, vlanId, deviceId, portName);
86 + } else {
87 + print(REMOVE_VLAN_FAILURE, vlanId, deviceId, portName);
88 + }
89 + }
90 +
91 +}
...@@ -161,6 +161,12 @@ ...@@ -161,6 +161,12 @@
161 </completers> 161 </completers>
162 </command> 162 </command>
163 <command> 163 <command>
164 + <action class="org.onosproject.cli.net.DeviceInterfaceRemoveCommand"/>
165 + <completers>
166 + <ref component-id="deviceIdCompleter"/>
167 + </completers>
168 + </command>
169 + <command>
164 <action class="org.onosproject.cli.net.AddMeter"/> 170 <action class="org.onosproject.cli.net.AddMeter"/>
165 <completers> 171 <completers>
166 <ref component-id="deviceIdCompleter"/> 172 <ref component-id="deviceIdCompleter"/>
......
...@@ -34,6 +34,15 @@ public interface InterfaceConfig extends HandlerBehaviour { ...@@ -34,6 +34,15 @@ public interface InterfaceConfig extends HandlerBehaviour {
34 boolean addInterfaceToVlan(DeviceId deviceId, String intf, VlanId vlanId); 34 boolean addInterfaceToVlan(DeviceId deviceId, String intf, VlanId vlanId);
35 35
36 /** 36 /**
37 + * Removes an interface from a VLAN.
38 + * @param deviceId the device ID
39 + * @param intf the name of the interface
40 + * @param vlanId the VLAN ID
41 + * @return the result of operation
42 + */
43 + boolean removeInterfaceFromVlan(DeviceId deviceId, String intf, VlanId vlanId);
44 +
45 + /**
37 * Configures an interface as trunk for VLAN. 46 * Configures an interface as trunk for VLAN.
38 * @param deviceId the device ID 47 * @param deviceId the device ID
39 * @param intf the name of the interface 48 * @param intf the name of the interface
...@@ -42,8 +51,19 @@ public interface InterfaceConfig extends HandlerBehaviour { ...@@ -42,8 +51,19 @@ public interface InterfaceConfig extends HandlerBehaviour {
42 */ 51 */
43 boolean addTrunkInterface(DeviceId deviceId, String intf, VlanId vlanId); 52 boolean addTrunkInterface(DeviceId deviceId, String intf, VlanId vlanId);
44 53
45 - /* TODO Addition of more methods to make the behavior symmetrical. 54 + /**
46 - Methods removeVlanFromInterface, getInterfacesForVlan, getVlansForInterface 55 + * Removes trunk mode configuration for VLAN from an interface.
47 - should be added to complete the behavior. 56 + * @param deviceId the device ID
57 + * @param intf the name of the interface
58 + * @param vlanId the VLAN ID
59 + * @return the result of operation
60 + */
61 + boolean removeTrunkInterface(DeviceId deviceId, String intf, VlanId vlanId);
62 +
63 + /**
64 + * TODO Addition of more methods to make the behavior symmetrical.
65 + * Methods getInterfacesForVlan, getVlansForInterface, getTrunkforInterface,
66 + * getInterfacesForTrunk should be added to complete the behavior.
48 */ 67 */
68 +
49 } 69 }
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
18 <feature name="${project.artifactId}" version="${project.version}" 18 <feature name="${project.artifactId}" version="${project.version}"
19 description="${project.description}"> 19 description="${project.description}">
20 <feature>onos-api</feature> 20 <feature>onos-api</feature>
21 + <bundle>mvn:${project.groupId}/onos-drivers-netconf/${project.version}</bundle>
22 +
21 <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle> 23 <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
22 24
23 <bundle>mvn:${project.groupId}/onos-drivers-utilities/${project.version}</bundle> 25 <bundle>mvn:${project.groupId}/onos-drivers-utilities/${project.version}</bundle>
......
...@@ -35,9 +35,9 @@ import static com.google.common.base.Preconditions.checkNotNull; ...@@ -35,9 +35,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
35 import static org.slf4j.LoggerFactory.getLogger; 35 import static org.slf4j.LoggerFactory.getLogger;
36 36
37 /** 37 /**
38 - * Configures interfaces on Cisco SM-X devices. 38 + * Configures interfaces on Cisco IOS devices.
39 */ 39 */
40 -public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour 40 +public class InterfaceConfigCiscoIosImpl extends AbstractHandlerBehaviour
41 implements InterfaceConfig { 41 implements InterfaceConfig {
42 42
43 private final Logger log = getLogger(getClass()); 43 private final Logger log = getLogger(getClass());
...@@ -58,9 +58,10 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -58,9 +58,10 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
58 .data().deviceId()).getSession(); 58 .data().deviceId()).getSession();
59 String reply; 59 String reply;
60 try { 60 try {
61 + //TODO remove XML triming if preceeds in Session
61 reply = session.requestSync(addInterfaceToVlanBuilder(intf, vlanId)).trim(); 62 reply = session.requestSync(addInterfaceToVlanBuilder(intf, vlanId)).trim();
62 } catch (NetconfException e) { 63 } catch (NetconfException e) {
63 - log.error("Failed to configure VLAN ID {} on device {} port {}.", 64 + log.error("Failed to configure VLAN ID {} on device {} interface {}.",
64 vlanId, deviceId, intf, e); 65 vlanId, deviceId, intf, e);
65 return false; 66 return false;
66 } 67 }
...@@ -70,14 +71,14 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -70,14 +71,14 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
70 } 71 }
71 72
72 /** 73 /**
73 - * Builds a request crafted to add an interface to a VLAN. 74 + * Builds a request to add an interface to a VLAN.
74 * @param intf the name of the interface 75 * @param intf the name of the interface
75 * @param vlanId the VLAN ID 76 * @param vlanId the VLAN ID
76 * @return the request string. 77 * @return the request string.
77 */ 78 */
78 private String addInterfaceToVlanBuilder(String intf, VlanId vlanId) { 79 private String addInterfaceToVlanBuilder(String intf, VlanId vlanId) {
79 - StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 80 + StringBuilder rpc =
80 - rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "); 81 + new StringBuilder("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
81 rpc.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"); 82 rpc.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
82 rpc.append("<edit-config>"); 83 rpc.append("<edit-config>");
83 rpc.append("<target>"); 84 rpc.append("<target>");
...@@ -105,6 +106,70 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -105,6 +106,70 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
105 } 106 }
106 107
107 /** 108 /**
109 + * Removes an interface from a VLAN.
110 + * @param deviceId the device ID
111 + * @param intf the name of the interface
112 + * @param vlanId the VLAN ID
113 + * @return the result of operation
114 + */
115 + @Override
116 + public boolean removeInterfaceFromVlan(DeviceId deviceId, String intf,
117 + VlanId vlanId) {
118 + NetconfController controller = checkNotNull(handler()
119 + .get(NetconfController.class));
120 +
121 + NetconfSession session = controller.getDevicesMap().get(handler()
122 + .data().deviceId()).getSession();
123 + String reply;
124 + try {
125 + //TODO remove XML triming if preceeds in Session
126 + reply = session.requestSync(removeInterfaceFromVlanBuilder(intf, vlanId)).trim();
127 + } catch (NetconfException e) {
128 + log.error("Failed to remove VLAN ID {} from device {} interface {}.",
129 + vlanId, deviceId, intf, e);
130 + return false;
131 + }
132 +
133 + return XmlConfigParser.configSuccess(XmlConfigParser.loadXml(
134 + new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8))));
135 + }
136 +
137 + /**
138 + * Builds a request to remove an interface from a VLAN.
139 + * @param intf the name of the interface
140 + * @param vlanId the VLAN ID
141 + * @return the request string.
142 + */
143 + private String removeInterfaceFromVlanBuilder(String intf, VlanId vlanId) {
144 + StringBuilder rpc =
145 + new StringBuilder("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
146 + rpc.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
147 + rpc.append("<edit-config>");
148 + rpc.append("<target>");
149 + rpc.append("<running/>");
150 + rpc.append("</target>");
151 + rpc.append("<config>");
152 + rpc.append("<xml-config-data>");
153 + rpc.append("<Device-Configuration><interface><Param>");
154 + rpc.append(intf);
155 + rpc.append("</Param>");
156 + rpc.append("<ConfigIf-Configuration>");
157 + rpc.append("<switchport operation=\"delete\"><access><vlan><VLANIDVLANPortAccessMode>");
158 + rpc.append(vlanId);
159 + rpc.append("</VLANIDVLANPortAccessMode></vlan></access></switchport>");
160 + rpc.append("<switchport operation=\"delete\"><mode><access/></mode></switchport>");
161 + rpc.append("</ConfigIf-Configuration>");
162 + rpc.append("</interface>");
163 + rpc.append("</Device-Configuration>");
164 + rpc.append("</xml-config-data>");
165 + rpc.append("</config>");
166 + rpc.append("</edit-config>");
167 + rpc.append("</rpc>");
168 +
169 + return rpc.toString();
170 + }
171 +
172 + /**
108 * Configures an interface as trunk for VLAN. 173 * Configures an interface as trunk for VLAN.
109 * @param deviceId the device ID 174 * @param deviceId the device ID
110 * @param intf the name of the interface 175 * @param intf the name of the interface
...@@ -120,9 +185,10 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -120,9 +185,10 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
120 .data().deviceId()).getSession(); 185 .data().deviceId()).getSession();
121 String reply; 186 String reply;
122 try { 187 try {
123 - reply = session.requestSync(addTrunkInterface(intf, vlanId)).trim(); 188 + //TODO remove XML triming if preceeds in Session
189 + reply = session.requestSync(addTrunkInterfaceBuilder(intf, vlanId)).trim();
124 } catch (NetconfException e) { 190 } catch (NetconfException e) {
125 - log.error("Failed to configure VLAN ID {} on device {} port {}.", 191 + log.error("Failed to configure trunk mode for VLAN ID {} on device {} interface {}.",
126 vlanId, deviceId, intf, e); 192 vlanId, deviceId, intf, e);
127 return false; 193 return false;
128 } 194 }
...@@ -132,14 +198,14 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -132,14 +198,14 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
132 } 198 }
133 199
134 /** 200 /**
135 - * Builds a request crafted to configure an interface as trunk for VLAN. 201 + * Builds a request to configure an interface as trunk for VLAN.
136 * @param intf the name of the interface 202 * @param intf the name of the interface
137 * @param vlanId the VLAN ID 203 * @param vlanId the VLAN ID
138 * @return the request string. 204 * @return the request string.
139 */ 205 */
140 - private String addTrunkInterface(String intf, VlanId vlanId) { 206 + private String addTrunkInterfaceBuilder(String intf, VlanId vlanId) {
141 - StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 207 + StringBuilder rpc =
142 - rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "); 208 + new StringBuilder("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
143 rpc.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"); 209 rpc.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
144 rpc.append("<edit-config>"); 210 rpc.append("<edit-config>");
145 rpc.append("<target>"); 211 rpc.append("<target>");
...@@ -151,11 +217,12 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -151,11 +217,12 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
151 rpc.append(intf); 217 rpc.append(intf);
152 rpc.append("</Param>"); 218 rpc.append("</Param>");
153 rpc.append("<ConfigIf-Configuration>"); 219 rpc.append("<ConfigIf-Configuration>");
154 - rpc.append("<switchport><trunk><encapsulation><dot1q/></encapsulation></trunk></switchport>"); 220 + rpc.append("<switchport><trunk><encapsulation><dot1q/></encapsulation>");
155 - rpc.append("<switchport><trunk><allowed><vlan><VLANIDsAllowedVLANsPortTrunkingMode>"); 221 + rpc.append("</trunk></switchport><switchport><trunk><allowed><vlan>");
222 + rpc.append("<VLANIDsAllowedVLANsPortTrunkingMode>");
156 rpc.append(vlanId); 223 rpc.append(vlanId);
157 - rpc.append("</VLANIDsAllowedVLANsPortTrunkingMode></vlan></allowed></trunk></switchport>"); 224 + rpc.append("</VLANIDsAllowedVLANsPortTrunkingMode></vlan></allowed></trunk>");
158 - rpc.append("<switchport><mode><trunk/></mode></switchport>"); 225 + rpc.append("</switchport><switchport><mode><trunk/></mode></switchport>");
159 rpc.append("</ConfigIf-Configuration>"); 226 rpc.append("</ConfigIf-Configuration>");
160 rpc.append("</interface>"); 227 rpc.append("</interface>");
161 rpc.append("</Device-Configuration>"); 228 rpc.append("</Device-Configuration>");
...@@ -167,5 +234,71 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour ...@@ -167,5 +234,71 @@ public class InterfaceConfigCiscoSmXImpl extends AbstractHandlerBehaviour
167 return rpc.toString(); 234 return rpc.toString();
168 } 235 }
169 236
237 + /**
238 + * Removes trunk mode configuration for VLAN from an interface.
239 + * @param deviceId the device ID
240 + * @param intf the name of the interface
241 + * @param vlanId the VLAN ID
242 + * @return the result of operation
243 + */
244 + @Override
245 + public boolean removeTrunkInterface(DeviceId deviceId, String intf, VlanId vlanId) {
246 + NetconfController controller = checkNotNull(handler()
247 + .get(NetconfController.class));
248 +
249 + NetconfSession session = controller.getDevicesMap().get(handler()
250 + .data().deviceId()).getSession();
251 + String reply;
252 + try {
253 + //TODO remove XML triming if preceeds in Session
254 + reply = session.requestSync(removeTrunkInterfaceBuilder(intf, vlanId)).trim();
255 + } catch (NetconfException e) {
256 + log.error("Failed to remove trunk mode for VLAN ID {} on device {} interface {}.",
257 + vlanId, deviceId, intf, e);
258 + return false;
259 + }
260 +
261 + return XmlConfigParser.configSuccess(XmlConfigParser.loadXml(
262 + new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8))));
263 +}
264 +
265 + /**
266 + * Builds a request to remove trunk mode configuration for VLAN from an interface.
267 + * @param intf the name of the interface
268 + * @param vlanId the VLAN ID
269 + * @return the request string.
270 + */
271 + private String removeTrunkInterfaceBuilder(String intf, VlanId vlanId) {
272 + StringBuilder rpc =
273 + new StringBuilder("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
274 + rpc.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
275 + rpc.append("<edit-config>");
276 + rpc.append("<target>");
277 + rpc.append("<running/>");
278 + rpc.append("</target>");
279 + rpc.append("<config>");
280 + rpc.append("<xml-config-data>");
281 + rpc.append("<Device-Configuration><interface><Param>");
282 + rpc.append(intf);
283 + rpc.append("</Param>");
284 + rpc.append("<ConfigIf-Configuration>");
285 + rpc.append("<switchport><mode operation=\"delete\"><trunk/></mode></switchport>");
286 + rpc.append("<switchport><trunk operation=\"delete\"><encapsulation>");
287 + rpc.append("<dot1q/></encapsulation></trunk></switchport>");
288 + rpc.append("<switchport><trunk operation=\"delete\"><allowed><vlan>");
289 + rpc.append("<VLANIDsAllowedVLANsPortTrunkingMode>");
290 + rpc.append(vlanId);
291 + rpc.append("</VLANIDsAllowedVLANsPortTrunkingMode></vlan></allowed>");
292 + rpc.append("</trunk></switchport></ConfigIf-Configuration>");
293 + rpc.append("</interface>");
294 + rpc.append("</Device-Configuration>");
295 + rpc.append("</xml-config-data>");
296 + rpc.append("</config>");
297 + rpc.append("</edit-config>");
298 + rpc.append("</rpc>");
299 +
300 + return rpc.toString();
301 + }
302 +
170 } 303 }
171 304
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
16 --> 16 -->
17 <drivers> 17 <drivers>
18 <driver name="cisco-netconf" extends="netconf" manufacturer="Cisco" 18 <driver name="cisco-netconf" extends="netconf" manufacturer="Cisco"
19 - hwVersion="SM-X" swVersion="IOS 15"> 19 + hwVersion="" swVersion="IOS">
20 <behaviour api="org.onosproject.net.behaviour.InterfaceConfig" 20 <behaviour api="org.onosproject.net.behaviour.InterfaceConfig"
21 - impl="org.onosproject.drivers.cisco.InterfaceConfigCiscoSmXImpl"/> 21 + impl="org.onosproject.drivers.cisco.InterfaceConfigCiscoIosImpl"/>
22 </driver> 22 </driver>
23 </drivers> 23 </drivers>
......