Jonathan Hart
Committed by Gerrit Code Review

Add basic ability to add and remove interfaces

Change-Id: I11fd764304a4e7ab298336f3033a4417ba4be28d
1 +/*
2 + * Copyright 2015 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 +
17 +package org.onosproject.cli.net;
18 +
19 +import com.google.common.collect.Sets;
20 +import org.apache.karaf.shell.commands.Command;
21 +import org.apache.karaf.shell.commands.Option;
22 +import org.onlab.packet.MacAddress;
23 +import org.onlab.packet.VlanId;
24 +import org.onosproject.cli.AbstractShellCommand;
25 +import org.onosproject.incubator.net.intf.Interface;
26 +import org.onosproject.incubator.net.intf.InterfaceAdminService;
27 +import org.onosproject.net.ConnectPoint;
28 +import org.onosproject.net.host.InterfaceIpAddress;
29 +
30 +import java.util.Set;
31 +
32 +/**
33 + * Adds a new interface configuration.
34 + */
35 +@Command(scope = "onos", name = "add-interface",
36 + description = "Adds a new configured interface")
37 +public class InterfaceAddCommand extends AbstractShellCommand {
38 +
39 + @Option(name = "-c", aliases = "--connectPoint",
40 + description = "Device port that the interface is associated with",
41 + required = true, multiValued = false)
42 + private String connectPoint = null;
43 +
44 + @Option(name = "-m", aliases = "--mac",
45 + description = "MAC address of the interface",
46 + required = true, multiValued = false)
47 + private String mac = null;
48 +
49 + @Option(name = "-i", aliases = "--ip",
50 + description = "IP address configured on the interface\n" +
51 + "(e.g. 10.0.1.1/24). Can be specified multiple times.",
52 + required = false, multiValued = true)
53 + private String[] ips = null;
54 +
55 + @Option(name = "-v", aliases = "--vlan",
56 + description = "VLAN configured on the interface",
57 + required = false, multiValued = false)
58 + private String vlan = null;
59 +
60 + @Override
61 + protected void execute() {
62 + InterfaceAdminService interfaceService = get(InterfaceAdminService.class);
63 +
64 + Set<InterfaceIpAddress> ipAddresses = Sets.newHashSet();
65 + if (ips != null) {
66 + for (String strIp : ips) {
67 + ipAddresses.add(InterfaceIpAddress.valueOf(strIp));
68 + }
69 + }
70 +
71 + VlanId vlanId = vlan == null ? VlanId.NONE : VlanId.vlanId(Short.parseShort(vlan));
72 +
73 + Interface intf = new Interface(ConnectPoint.deviceConnectPoint(connectPoint),
74 + ipAddresses, MacAddress.valueOf(mac), vlanId);
75 +
76 + interfaceService.add(intf);
77 + }
78 +
79 +}
1 +/*
2 + * Copyright 2015 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 +
17 +package org.onosproject.cli.net;
18 +
19 +import org.apache.karaf.shell.commands.Argument;
20 +import org.apache.karaf.shell.commands.Command;
21 +import org.onlab.packet.VlanId;
22 +import org.onosproject.cli.AbstractShellCommand;
23 +import org.onosproject.incubator.net.intf.InterfaceAdminService;
24 +import org.onosproject.net.ConnectPoint;
25 +
26 +/**
27 + * Removes an interface configuration.
28 + */
29 +@Command(scope = "onos", name = "remove-interface",
30 + description = "Removes a configured interface")
31 +public class InterfaceRemoveCommand extends AbstractShellCommand {
32 +
33 + @Argument(index = 0, name = "connectPoint",
34 + description = "Connect point of the interface",
35 + required = true, multiValued = false)
36 + private String connectPoint = null;
37 +
38 + @Argument(index = 1, name = "vlan",
39 + description = "Interface vlan",
40 + required = true, multiValued = false)
41 + private String vlan = null;
42 +
43 + @Override
44 + protected void execute() {
45 + InterfaceAdminService interfaceService = get(InterfaceAdminService.class);
46 +
47 + interfaceService.remove(ConnectPoint.deviceConnectPoint(connectPoint),
48 + VlanId.vlanId(Short.parseShort(vlan)));
49 + }
50 +
51 +}
...@@ -352,6 +352,19 @@ ...@@ -352,6 +352,19 @@
352 <action class="org.onosproject.cli.net.InterfacesListCommand"/> 352 <action class="org.onosproject.cli.net.InterfacesListCommand"/>
353 </command> 353 </command>
354 <command> 354 <command>
355 + <action class="org.onosproject.cli.net.InterfaceAddCommand"/>
356 + <optional-completers>
357 + <entry key="-c" value-ref="connectPointCompleter"/>
358 + <entry key="--connectPoint" value-ref="connectPointCompleter"/>
359 + </optional-completers>
360 + </command>
361 + <command>
362 + <action class="org.onosproject.cli.net.InterfaceRemoveCommand"/>
363 + <completers>
364 + <ref component-id="connectPointCompleter"/>
365 + </completers>
366 + </command>
367 + <command>
355 <action class="org.onosproject.cli.net.GroupsListCommand"/> 368 <action class="org.onosproject.cli.net.GroupsListCommand"/>
356 </command> 369 </command>
357 370
......
...@@ -17,13 +17,15 @@ ...@@ -17,13 +17,15 @@
17 package org.onosproject.incubator.net.config.basics; 17 package org.onosproject.incubator.net.config.basics;
18 18
19 import com.fasterxml.jackson.databind.JsonNode; 19 import com.fasterxml.jackson.databind.JsonNode;
20 +import com.fasterxml.jackson.databind.node.ArrayNode;
21 +import com.fasterxml.jackson.databind.node.ObjectNode;
20 import com.google.common.annotations.Beta; 22 import com.google.common.annotations.Beta;
21 import com.google.common.collect.Sets; 23 import com.google.common.collect.Sets;
22 import org.onlab.packet.MacAddress; 24 import org.onlab.packet.MacAddress;
23 import org.onlab.packet.VlanId; 25 import org.onlab.packet.VlanId;
24 -import org.onosproject.net.config.Config;
25 import org.onosproject.incubator.net.intf.Interface; 26 import org.onosproject.incubator.net.intf.Interface;
26 import org.onosproject.net.ConnectPoint; 27 import org.onosproject.net.ConnectPoint;
28 +import org.onosproject.net.config.Config;
27 import org.onosproject.net.host.InterfaceIpAddress; 29 import org.onosproject.net.host.InterfaceIpAddress;
28 30
29 import java.util.Set; 31 import java.util.Set;
...@@ -37,7 +39,6 @@ public class InterfaceConfig extends Config<ConnectPoint> { ...@@ -37,7 +39,6 @@ public class InterfaceConfig extends Config<ConnectPoint> {
37 public static final String MAC = "mac"; 39 public static final String MAC = "mac";
38 public static final String VLAN = "vlan"; 40 public static final String VLAN = "vlan";
39 41
40 - public static final String IP_MISSING_ERROR = "Must have at least one IP address";
41 public static final String MAC_MISSING_ERROR = "Must have a MAC address for each interface"; 42 public static final String MAC_MISSING_ERROR = "Must have a MAC address for each interface";
42 public static final String CONFIG_VALUE_ERROR = "Error parsing config value"; 43 public static final String CONFIG_VALUE_ERROR = "Error parsing config value";
43 44
...@@ -53,9 +54,6 @@ public class InterfaceConfig extends Config<ConnectPoint> { ...@@ -53,9 +54,6 @@ public class InterfaceConfig extends Config<ConnectPoint> {
53 try { 54 try {
54 for (JsonNode intfNode : array) { 55 for (JsonNode intfNode : array) {
55 Set<InterfaceIpAddress> ips = getIps(intfNode); 56 Set<InterfaceIpAddress> ips = getIps(intfNode);
56 - if (ips.isEmpty()) {
57 - throw new ConfigException(IP_MISSING_ERROR);
58 - }
59 57
60 if (intfNode.path(MAC).isMissingNode()) { 58 if (intfNode.path(MAC).isMissingNode()) {
61 throw new ConfigException(MAC_MISSING_ERROR); 59 throw new ConfigException(MAC_MISSING_ERROR);
...@@ -63,10 +61,7 @@ public class InterfaceConfig extends Config<ConnectPoint> { ...@@ -63,10 +61,7 @@ public class InterfaceConfig extends Config<ConnectPoint> {
63 61
64 MacAddress mac = MacAddress.valueOf(intfNode.path(MAC).asText()); 62 MacAddress mac = MacAddress.valueOf(intfNode.path(MAC).asText());
65 63
66 - VlanId vlan = VlanId.NONE; 64 + VlanId vlan = getVlan(intfNode);
67 - if (!intfNode.path(VLAN).isMissingNode()) {
68 - vlan = VlanId.vlanId(Short.valueOf(intfNode.path(VLAN).asText()));
69 - }
70 65
71 interfaces.add(new Interface(subject, ips, mac, vlan)); 66 interfaces.add(new Interface(subject, ips, mac, vlan));
72 } 67 }
...@@ -77,13 +72,64 @@ public class InterfaceConfig extends Config<ConnectPoint> { ...@@ -77,13 +72,64 @@ public class InterfaceConfig extends Config<ConnectPoint> {
77 return interfaces; 72 return interfaces;
78 } 73 }
79 74
75 + /**
76 + * Adds an interface to the config.
77 + *
78 + * @param intf interface to add
79 + */
80 + public void addInterface(Interface intf) {
81 + ObjectNode intfNode = array.addObject();
82 + intfNode.put(MAC, intf.mac().toString());
83 +
84 + if (!intf.ipAddresses().isEmpty()) {
85 + intfNode.set(IPS, putIps(intf.ipAddresses()));
86 + }
87 +
88 + if (!intf.vlan().equals(VlanId.NONE)) {
89 + intfNode.put(VLAN, intf.vlan().toString());
90 + }
91 + }
92 +
93 + /**
94 + * Removes an interface from the config.
95 + *
96 + * @param intf interface to remove
97 + */
98 + public void removeInterface(Interface intf) {
99 + for (int i = 0; i < array.size(); i++) {
100 + if (intf.vlan().equals(getVlan(node))) {
101 + array.remove(i);
102 + break;
103 + }
104 + }
105 + }
106 +
107 + private VlanId getVlan(JsonNode node) {
108 + VlanId vlan = VlanId.NONE;
109 + if (!node.path(VLAN).isMissingNode()) {
110 + vlan = VlanId.vlanId(Short.valueOf(node.path(VLAN).asText()));
111 + }
112 + return vlan;
113 + }
114 +
80 private Set<InterfaceIpAddress> getIps(JsonNode node) { 115 private Set<InterfaceIpAddress> getIps(JsonNode node) {
81 Set<InterfaceIpAddress> ips = Sets.newHashSet(); 116 Set<InterfaceIpAddress> ips = Sets.newHashSet();
82 117
83 JsonNode ipsNode = node.get(IPS); 118 JsonNode ipsNode = node.get(IPS);
84 - ipsNode.forEach(jsonNode -> ips.add(InterfaceIpAddress.valueOf(jsonNode.asText()))); 119 + if (ipsNode != null) {
120 + ipsNode.forEach(jsonNode ->
121 + ips.add(InterfaceIpAddress.valueOf(jsonNode.asText())));
122 + }
85 123
86 return ips; 124 return ips;
87 } 125 }
88 126
127 + private ArrayNode putIps(Set<InterfaceIpAddress> intfIpAddresses) {
128 + ArrayNode ipArray = mapper.createArrayNode();
129 +
130 + intfIpAddresses.forEach(i -> ipArray.add(i.toString()));
131 +
132 + return ipArray;
133 + }
134 +
89 } 135 }
......
1 +/*
2 + * Copyright 2015 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 +
17 +package org.onosproject.incubator.net.intf;
18 +
19 +import org.onlab.packet.VlanId;
20 +import org.onosproject.net.ConnectPoint;
21 +
22 +/**
23 + * Provides a means to modify the interfaces configuration.
24 + */
25 +public interface InterfaceAdminService {
26 + /**
27 + * Adds a new interface configuration to the system.
28 + *
29 + * @param intf interface to add
30 + */
31 + void add(Interface intf);
32 +
33 + /**
34 + * Removes an interface configuration from the system.
35 + *
36 + * @param connectPoint connect point of the interface
37 + * @param vlanId vlan id
38 + */
39 + void remove(ConnectPoint connectPoint, VlanId vlanId);
40 +}
...@@ -29,6 +29,7 @@ import org.onlab.packet.VlanId; ...@@ -29,6 +29,7 @@ import org.onlab.packet.VlanId;
29 import org.onosproject.incubator.net.config.basics.ConfigException; 29 import org.onosproject.incubator.net.config.basics.ConfigException;
30 import org.onosproject.incubator.net.config.basics.InterfaceConfig; 30 import org.onosproject.incubator.net.config.basics.InterfaceConfig;
31 import org.onosproject.incubator.net.intf.Interface; 31 import org.onosproject.incubator.net.intf.Interface;
32 +import org.onosproject.incubator.net.intf.InterfaceAdminService;
32 import org.onosproject.incubator.net.intf.InterfaceService; 33 import org.onosproject.incubator.net.intf.InterfaceService;
33 import org.onosproject.net.ConnectPoint; 34 import org.onosproject.net.ConnectPoint;
34 import org.onosproject.net.config.NetworkConfigEvent; 35 import org.onosproject.net.config.NetworkConfigEvent;
...@@ -50,7 +51,8 @@ import static java.util.stream.Collectors.toSet; ...@@ -50,7 +51,8 @@ import static java.util.stream.Collectors.toSet;
50 */ 51 */
51 @Service 52 @Service
52 @Component(immediate = true) 53 @Component(immediate = true)
53 -public class InterfaceManager implements InterfaceService { 54 +public class InterfaceManager implements InterfaceService,
55 + InterfaceAdminService {
54 56
55 private final Logger log = LoggerFactory.getLogger(getClass()); 57 private final Logger log = LoggerFactory.getLogger(getClass());
56 58
...@@ -153,6 +155,54 @@ public class InterfaceManager implements InterfaceService { ...@@ -153,6 +155,54 @@ public class InterfaceManager implements InterfaceService {
153 interfaces.remove(port); 155 interfaces.remove(port);
154 } 156 }
155 157
158 + @Override
159 + public void add(Interface intf) {
160 + if (interfaces.containsKey(intf.connectPoint())) {
161 + boolean conflict = interfaces.get(intf.connectPoint()).stream()
162 + .filter(i -> i.connectPoint().equals(intf.connectPoint()))
163 + .filter(i -> i.mac().equals(intf.mac()))
164 + .filter(i -> i.vlan().equals(intf.vlan()))
165 + .findAny().isPresent();
166 +
167 + if (conflict) {
168 + log.error("Can't add interface because it conflicts with existing config");
169 + return;
170 + }
171 + }
172 +
173 + InterfaceConfig config =
174 + configService.addConfig(intf.connectPoint(), CONFIG_CLASS);
175 +
176 + config.addInterface(intf);
177 +
178 + configService.applyConfig(intf.connectPoint(), CONFIG_CLASS, config.node());
179 + }
180 +
181 + @Override
182 + public void remove(ConnectPoint connectPoint, VlanId vlanId) {
183 + Optional<Interface> intf = interfaces.get(connectPoint).stream()
184 + .filter(i -> i.vlan().equals(vlanId))
185 + .findAny();
186 +
187 + if (!intf.isPresent()) {
188 + log.error("Can't find interface {}/{} to remove", connectPoint, vlanId);
189 + return;
190 + }
191 +
192 + InterfaceConfig config = configService.addConfig(intf.get().connectPoint(), CONFIG_CLASS);
193 + config.removeInterface(intf.get());
194 +
195 + try {
196 + if (config.getInterfaces().isEmpty()) {
197 + configService.removeConfig(connectPoint, CONFIG_CLASS);
198 + } else {
199 + configService.applyConfig(intf.get().connectPoint(), CONFIG_CLASS, config.node());
200 + }
201 + } catch (ConfigException e) {
202 + log.error("Error reading interfaces JSON", e);
203 + }
204 + }
205 +
156 /** 206 /**
157 * Listener for network config events. 207 * Listener for network config events.
158 */ 208 */
......