Add missing validation on ContainerFlowConfig objects
[controller.git] / opendaylight / containermanager / api / src / main / java / org / opendaylight / controller / containermanager / ContainerConfig.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.containermanager;
11
12 import java.io.Serializable;
13 import java.util.ArrayList;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17
18 import javax.xml.bind.annotation.XmlAccessType;
19 import javax.xml.bind.annotation.XmlAccessorType;
20 import javax.xml.bind.annotation.XmlElement;
21 import javax.xml.bind.annotation.XmlRootElement;
22
23 import org.opendaylight.controller.sal.core.ContainerFlow;
24 import org.opendaylight.controller.sal.core.NodeConnector;
25 import org.opendaylight.controller.sal.match.Match;
26 import org.opendaylight.controller.sal.utils.GlobalConstants;
27 import org.opendaylight.controller.sal.utils.Status;
28 import org.opendaylight.controller.sal.utils.StatusCode;
29
30 /**
31  * Container Configuration Java Object for Container Manager Represents a container
32  * configuration information for Container Manager.
33  *
34  * Objects of this class are also serialized to and deserialized from binary
35  * files through java serialization API when saving to/reading from Container
36  * Manager startup configuration file.
37  */
38 @XmlRootElement(name = "container-config")
39 @XmlAccessorType(XmlAccessType.NONE)
40 public class ContainerConfig implements Serializable {
41     private static final long serialVersionUID = 2L;
42     private static final String regexName = "^\\w+$";
43
44     @XmlElement
45     private String container;
46
47     @XmlElement
48     private String staticVlan;
49
50     @XmlElement(name = "nodeConnectors")
51     private List<String> ports;
52
53     @XmlElement(name = "flowSpecs")
54     private List<ContainerFlowConfig> containerFlows;
55
56     public String getContainer() {
57         return container;
58     }
59
60     public void setContainer(String container) {
61         this.container = container;
62     }
63
64     public String getStaticVlan() {
65         return staticVlan;
66     }
67
68     public void setStaticVlan(String staticVlan) {
69         this.staticVlan = staticVlan;
70     }
71
72     public List<ContainerFlowConfig> getContainerFlows() {
73         return containerFlows;
74     }
75
76     public void setContainerFlows(List<ContainerFlowConfig> containerFlows) {
77         this.containerFlows = containerFlows;
78     }
79
80     public static long getSerialversionuid() {
81         return serialVersionUID;
82     }
83
84     public static String getRegexname() {
85         return regexName;
86     }
87
88     public void setPorts(List<String> ports) {
89         this.ports = ports;
90     }
91
92     /**
93      * Default constructor needed by Gson.
94      *
95      * @return a Default ContainerConfig
96      */
97     public ContainerConfig() {
98         this.container = null;
99         this.staticVlan = null;
100         this.ports = new ArrayList<String>(0);
101         this.containerFlows = new ArrayList<ContainerFlowConfig>(0);
102     }
103
104     /**
105      * Constructor for the ContainerConfig.
106      *
107      * @param container
108      *            Name of the container in this configuration
109      * @param vlan
110      *            vlan assigned to this container
111      * @param nodeName
112      *            the name of the node assigned to the container from this
113      *            configuration
114      * @param ports
115      *            the list of NodeConnectors on the Node belonging to the container
116      * @return the constructed object
117      */
118     public ContainerConfig(String container, String vlan, List<String> portList, List<ContainerFlowConfig> containerFlows) {
119         this.container = container;
120         this.staticVlan = vlan;
121         this.ports = (portList == null) ? new ArrayList<String>(0) : new ArrayList<String>(portList);
122         this.containerFlows = (containerFlows == null) ? new ArrayList<ContainerFlowConfig>(0)
123                 : new ArrayList<ContainerFlowConfig>(containerFlows);
124     }
125
126     public ContainerConfig(ContainerConfig config) {
127         this.container = config.container;
128         this.staticVlan = config.staticVlan;
129         this.ports = (config.ports == null) ? new ArrayList<String>(0) : new ArrayList<String>(config.ports);
130         this.containerFlows = (config.containerFlows == null) ? new ArrayList<ContainerFlowConfig>(0)
131                 : new ArrayList<ContainerFlowConfig>(config.containerFlows);
132     }
133
134     /**
135      * Returns the container name.
136      *
137      * @return the container name
138      */
139     public String getContainerName() {
140         return container;
141     }
142
143     /**
144      * Returns the Vlan tag.
145      *
146      * @return the Vlan Tag configured for this container configuration
147      */
148     public String getVlanTag() {
149         return staticVlan;
150     }
151
152     /**
153      * Returns the configured ports.
154      *
155      * @return the string with the list of ports associated to the container on this
156      *         configuration
157      */
158     public List<String> getPorts() {
159         return new ArrayList<String>(ports);
160     }
161
162     /**
163      * Returns the list of container flows configured for this container
164      *
165      * @return
166      */
167     public List<ContainerFlowConfig> getContainerFlowConfigs() {
168         return (containerFlows == null || containerFlows.isEmpty()) ? new ArrayList<ContainerFlowConfig>(0)
169                 : new ArrayList<ContainerFlowConfig>(containerFlows);
170     }
171
172     /**
173      * Matches container name against passed parameter.
174      *
175      * @param name
176      *            name of the container to be matched
177      * @return true if the passed argument correspond with the container name in the
178      *         configuration
179      */
180     public boolean matchName(String name) {
181         return this.container.equals(name);
182     }
183
184     /**
185      * Parse the port list in several NodeConnector descriptor.
186      *
187      * @return the list of NodeConnector corresponding to the ports configured
188      *         on this configuration
189      */
190     public List<NodeConnector> getPortList() {
191         List<NodeConnector> portList = new ArrayList<NodeConnector>();
192         if (ports != null && !ports.isEmpty()) {
193             for (String portString : ports) {
194                 portList.add(NodeConnector.fromString(portString));
195             }
196         }
197         return portList;
198     }
199
200     /**
201      * Checks if this is a valid container configuration
202      *
203      * @return true, if is valid container configuration, false otherwise
204      */
205     public Status validate() {
206         Status status = validateName();
207         if (status.isSuccess()) {
208             status = validateStaticVlan();
209             if (status.isSuccess()) {
210                 status = validatePorts();
211                 if (status.isSuccess()) {
212                     status = validateContainerFlows();
213                 }
214             }
215         }
216         return status;
217     }
218
219     /**
220      * Checks for valid name.
221      *
222      * @return true, if successful
223      */
224     private Status validateName() {
225         // No Container configuration allowed to container default
226         return ((container != null) && container.matches(regexName) && !container.equalsIgnoreCase(GlobalConstants.DEFAULT.toString())) ?
227                 new Status(StatusCode.SUCCESS) : new Status(StatusCode.BADREQUEST, "Invalid container name");
228     }
229
230     /**
231      * Checks for valid ports.
232      *
233      * @return true, if successful
234      */
235     private Status validatePorts() {
236         return validateNodeConnectors(this.ports);
237     }
238
239     public static Status validateNodeConnectors(List<String> connectorList) {
240         if (connectorList != null && !connectorList.isEmpty()) {
241             for (String ncString : connectorList) {
242                 if (NodeConnector.fromString(ncString) == null) {
243                     return new Status(StatusCode.BADREQUEST, "Invalid node connector: " + ncString);
244                 }
245             }
246
247         }
248         return new Status(StatusCode.SUCCESS);
249     }
250
251     public static List<NodeConnector> nodeConnectorsFromString(List<String> nodeConnectorStrings) {
252         List<NodeConnector> list = new ArrayList<NodeConnector>(nodeConnectorStrings.size());
253         for (String str : nodeConnectorStrings) {
254             list.add(NodeConnector.fromString(str));
255         }
256         return list;
257     }
258
259     /**
260      * Checks for valid static vlan.
261      *
262      * @return true, if successful
263      */
264     private Status validateStaticVlan() {
265         if (staticVlan != null && !staticVlan.trim().isEmpty()) {
266             short vl = 0;
267             try {
268                 vl = Short.valueOf(staticVlan);
269             } catch (NumberFormatException e) {
270                 return new Status(StatusCode.BADREQUEST, "Static Vlan Value must be between 1 and 4095");
271             }
272             if ((vl < 1) || (vl > 4095)) {
273                 return new Status(StatusCode.BADREQUEST, "Static Vlan Value must be between 1 and 4095");
274             }
275         }
276         return new Status(StatusCode.SUCCESS);
277     }
278
279     private Status validateContainerFlows() {
280         if (containerFlows != null && !containerFlows.isEmpty()) {
281             for (ContainerFlowConfig conf : containerFlows) {
282                 Status status = conf.validate();
283                 if (!status.isSuccess()) {
284                     return new Status(StatusCode.BADREQUEST, "Invalid Flow Spec: " + status.getDescription());
285                 }
286             }
287         }
288         return new Status(StatusCode.SUCCESS);
289     }
290
291     /**
292      * Returns Vlan value in short
293      *
294      * @return the Vlan tag
295      */
296     public short getStaticVlanValue() {
297         if ((staticVlan == null) || (staticVlan.trim().isEmpty())) {
298             return 0;
299         }
300         try {
301             return Short.valueOf(staticVlan);
302         } catch (NumberFormatException e) {
303             return 0;
304         }
305     }
306
307     public Status addNodeConnectors(List<String> ncList) {
308         // Syntax check
309         Status status = ContainerConfig.validateNodeConnectors(ncList);
310         if (!status.isSuccess()) {
311             return status;
312         }
313
314         /* Allow adding ports which are already present
315         if (!ports.isEmpty()) {
316             List<String> intersection = new ArrayList<String>(ports);
317             intersection.retainAll(ncList);
318             if (!intersection.isEmpty()) {
319                 return new Status(StatusCode.CONFLICT, "The following node connectors are already part of this container: "
320                         + intersection);
321             }
322         }
323         */
324
325         // Add ports
326         ports.addAll(ncList);
327         return new Status(StatusCode.SUCCESS);
328     }
329
330     public Status removeNodeConnectors(List<String> ncList) {
331         // Syntax check
332         Status status = ContainerConfig.validateNodeConnectors(ncList);
333         if (!status.isSuccess()) {
334             return status;
335         }
336         // Presence check
337         if (ports.isEmpty()) {
338             return new Status(StatusCode.BADREQUEST, "The following node connectors are not part of this container: "
339                     + ncList);
340         }
341         List<String> extra = new ArrayList<String>(ncList);
342         extra.removeAll(ports);
343         if (!extra.isEmpty()) {
344             return new Status(StatusCode.CONFLICT, "The following node connectors are not part of this container: " + extra);
345         }
346         // Remove ports
347         ports.removeAll(ncList);
348         return new Status(StatusCode.SUCCESS);
349     }
350
351     public Status validateContainerFlowModify(List<ContainerFlowConfig> cFlowConfigs, boolean delete) {
352         // Sanity Check
353         if (cFlowConfigs == null || cFlowConfigs.isEmpty()) {
354             return new Status(StatusCode.BADREQUEST, "Invalid Flow Spec configuration(s): null or empty list");
355         }
356         // Validity check
357         for (ContainerFlowConfig cFlowConf : cFlowConfigs) {
358             Status status = cFlowConf.validate();
359             if (!status.isSuccess()) {
360                 return new Status(StatusCode.BADREQUEST, String.format("Invalid Flow Spec configuration (%s): %s",
361                         cFlowConf.getName(), status.getDescription()));
362             }
363         }
364         // Name conflict check
365         List<String> existingNames = this.getContainerFlowConfigsNames();
366         List<String> proposedNames = this.getContainerFlowConfigsNames(cFlowConfigs);
367
368         // Check for duplicates in the request
369         if (proposedNames.size() < cFlowConfigs.size()) {
370             return new Status(StatusCode.BADREQUEST,
371                     "Invalid Flow Spec configuration(s): duplicate name configs present");
372         }
373
374         // Check for overflow
375         if (delete) {
376             // Raw size check
377             if (proposedNames.size() > existingNames.size()) {
378                 return new Status(StatusCode.BADREQUEST,
379                         "Invalid request: requested to remove more flow spec configs than available ones");
380             }
381             // Presence check
382             for (ContainerFlowConfig config : cFlowConfigs) {
383                 if (!this.containerFlows.contains(config)) {
384                     return new Status(StatusCode.BADREQUEST, String.format(
385                             "Invalid request: requested to remove nonexistent flow spec config: %s",
386                             config.getName()));
387                 }
388             }
389         } else {
390             // Check for conflicting names with existing cFlows
391             List<String> conflicting = new ArrayList<String>(existingNames);
392             conflicting.retainAll(proposedNames);
393             if (!conflicting.isEmpty()) {
394                 return new Status(StatusCode.CONFLICT,
395                         "Invalid Flow Spec configuration: flow spec name(s) conflict with existing flow specs: "
396                                 + conflicting.toString());
397             }
398
399             /*
400              * Check for conflicting flow spec match (we only check for strict
401              * equality). Remove this in case (*) is reintroduced
402              */
403             if (this.containerFlows != null && !this.containerFlows.isEmpty()) {
404                 Set<Match> existingMatches = new HashSet<Match>();
405                 for (ContainerFlowConfig existing : this.containerFlows) {
406                     existingMatches.addAll(existing.getMatches());
407                 }
408                 for (ContainerFlowConfig proposed : cFlowConfigs) {
409                     if (existingMatches.removeAll(proposed.getMatches())) {
410                         return new Status(StatusCode.CONFLICT, String.format(
411                                 "Invalid Flow Spec configuration: %s conflicts with existing flow spec",
412                                 proposed.getName()));
413                     }
414                 }
415             }
416         }
417
418         /*
419          * Revisit the following flow-spec confict validation later based on more testing.
420          * (*)
421         if (!delete) {
422             // Check for overlapping container flows in the request
423             int size = cFlowConfigs.size();
424             for (int i = 0; i < size; i++) {
425                 ContainerFlowConfig first = cFlowConfigs.get(i);
426                 for (int j = i + 1; j < size; j++) {
427                     ContainerFlowConfig second = cFlowConfigs.get(j);
428                     if (first.overlap(second)) {
429                         return new Status(StatusCode.BADREQUEST, String.format(
430                                 "Invalid Request: the proposed flow specs overlap: %s <-> %s", first.getName(),
431                                 second.getName()));
432                     }
433                 }
434             }
435             // Check if any of the proposed container flows overlap with the
436             // existing ones
437             for (ContainerFlowConfig current : cFlowConfigs) {
438                 for (ContainerFlowConfig existing : this.containerFlows) {
439                     if (current.overlap(existing)) {
440                         return new Status(StatusCode.BADREQUEST, String.format(
441                                 "Invalid Request: the proposed flow specs overlap: %s <-> %s", current.getName(),
442                                 existing.getName()));
443                     }
444                 }
445             }
446         }
447         */
448
449         return new Status(StatusCode.SUCCESS);
450     }
451
452     public ContainerFlowConfig getContainerFlowConfig(String name) {
453         if (this.containerFlows != null && !this.containerFlows.isEmpty()) {
454             for (ContainerFlowConfig conf : this.containerFlows) {
455                 if (conf.getName().equals(name)) {
456                     return new ContainerFlowConfig(conf);
457                 }
458             }
459         }
460         return null;
461     }
462
463     public List<String> getContainerFlowConfigsNames() {
464         return getContainerFlowConfigsNames(this.containerFlows);
465     }
466
467     /**
468      * Returns the list of unique names for the passed list of
469      * ContainerFlowConfig objects. the list will not contain duplicates even
470      * though the passed object list has ContainerFlowConfig objects with same
471      * names
472      *
473      * @param confList
474      *            the list of ContainerFlowConfig objects
475      * @return the list of correspondent unique container flow names. The return
476      *         list may differ from the passed list in size, if the latter
477      *         contains duplicates
478      */
479     private List<String> getContainerFlowConfigsNames(List<ContainerFlowConfig> confList) {
480         // Use set to check for duplicates later
481         Set<String> namesSet = new HashSet<String>();
482         if (confList != null) {
483             for (ContainerFlowConfig conf : confList) {
484                 namesSet.add(conf.getName());
485             }
486         }
487         return new ArrayList<String>(namesSet);
488     }
489
490     /**
491      * Add the proposed list of container flow configurations to this container
492      * configuration. A validation check on the operation is first run.
493      *
494      * @param containerFlowConfigs
495      *            the proposed list of container flow configuration objects to
496      *            add to this container configuration object
497      * @return the result of this request as Status object
498      */
499     public Status addContainerFlows(List<ContainerFlowConfig> containerFlowConfigs) {
500         Status status = this.validateContainerFlowModify(containerFlowConfigs, false);
501         if (!status.isSuccess()) {
502             return status;
503         }
504         if (this.containerFlows.addAll(containerFlowConfigs) == false) {
505             return new Status(StatusCode.INTERNALERROR, "Unable to update the flow spec configuration(s)");
506         }
507         return new Status(StatusCode.SUCCESS);
508     }
509
510     public Status removeContainerFlows(List<ContainerFlowConfig> containerFlowConfigs) {
511         Status status = this.validateContainerFlowModify(containerFlowConfigs, true);
512         if (!status.isSuccess()) {
513             return status;
514         }
515         if (this.containerFlows.removeAll(containerFlowConfigs) == false) {
516             return new Status(StatusCode.INTERNALERROR, "Unable to update the flow spec configuration(s)");
517         }
518         return new Status(StatusCode.SUCCESS);
519     }
520
521     public Status removeContainerFlows(Set<String> names) {
522         // Sanity check
523         if (names == null || names.isEmpty()) {
524             return new Status(StatusCode.BADREQUEST, "Invalid flow spec names list");
525         }
526         // Validation check
527         List<String> present = this.getContainerFlowConfigsNames();
528         if (!present.containsAll(names)) {
529             List<String> notPresent = new ArrayList<String>(names);
530             notPresent.retainAll(present);
531             return new Status(StatusCode.BADREQUEST, "Following flow spec(s) are not present: " + notPresent);
532         }
533         // Remove
534         List<ContainerFlowConfig> toDelete = new ArrayList<ContainerFlowConfig>(names.size());
535         for (ContainerFlowConfig config : this.containerFlows) {
536             if (names.contains(config.getName())) {
537                 toDelete.add(config);
538             }
539         }
540         if (this.containerFlows.removeAll(toDelete) == false) {
541             return new Status(StatusCode.INTERNALERROR, "Unable to remove the flow spec configuration(s)");
542         }
543         return new Status(StatusCode.SUCCESS);
544     }
545
546     public List<ContainerFlowConfig> getContainerFlowConfigs(Set<String> names) {
547         List<ContainerFlowConfig> list = new ArrayList<ContainerFlowConfig>(names.size());
548         for (String name : names) {
549             ContainerFlowConfig conf = this.getContainerFlowConfig(name);
550             if (conf != null) {
551                 list.add(new ContainerFlowConfig(conf));
552             }
553         }
554         return list;
555     }
556
557     @Override
558     public int hashCode() {
559         final int prime = 31;
560         int result = 1;
561         result = prime * result + ((containerFlows == null) ? 0 : containerFlows.hashCode());
562         result = prime * result + ((ports == null) ? 0 : ports.hashCode());
563         result = prime * result + ((container == null) ? 0 : container.hashCode());
564         result = prime * result + ((staticVlan == null) ? 0 : staticVlan.hashCode());
565         return result;
566     }
567
568     @Override
569     public boolean equals(Object obj) {
570         if (this == obj) {
571             return true;
572         }
573         if (obj == null) {
574             return false;
575         }
576         if (getClass() != obj.getClass()) {
577             return false;
578         }
579         ContainerConfig other = (ContainerConfig) obj;
580         if (containerFlows == null) {
581             if (other.containerFlows != null) {
582                 return false;
583             }
584         } else if (!containerFlows.equals(other.containerFlows)) {
585             return false;
586         }
587         if (ports == null) {
588             if (other.ports != null) {
589                 return false;
590             }
591         } else if (!ports.equals(other.ports)) {
592             return false;
593         }
594         if (container == null) {
595             if (other.container != null) {
596                 return false;
597             }
598         } else if (!container.equals(other.container)) {
599             return false;
600         }
601         if (staticVlan == null) {
602             if (other.staticVlan != null) {
603                 return false;
604             }
605         } else if (!staticVlan.equals(other.staticVlan)) {
606             return false;
607         }
608         return true;
609     }
610
611
612     /*
613      * (non-Javadoc)
614      *
615      * @see java.lang.Object#toString()
616      */
617     @Override
618     public String toString() {
619         String vlString = "";
620         if (staticVlan != null) {
621             vlString = staticVlan;
622         }
623         return "container=" + container + ((vlString.equals("") ? "" : " static Vlan=" + vlString)) + " ports=" + ports + " flowspecs=" + containerFlows;
624     }
625
626     /**
627      * Returns whether this Container configuration object has any ports specified
628      *
629      * @return true if any port is specified, false otherwise
630      */
631     public boolean hasNodeConnectors() {
632         return (ports != null && !ports.isEmpty());
633     }
634
635     /**
636      * Returns whether this Container configuration object has any flow specs specified
637      *
638      * @return true if any flow spec is specified, false otherwise
639      */
640     public boolean hasFlowSpecs() {
641         return (containerFlows != null && !containerFlows.isEmpty());
642     }
643
644     public List<ContainerFlow> getContainerFlowSpecs() {
645         List<ContainerFlow> list = new ArrayList<ContainerFlow>();
646         if (containerFlows != null && !containerFlows.isEmpty()) {
647             for (ContainerFlowConfig flowSpec : containerFlows) {
648                 for (Match match : flowSpec.getMatches()) {
649                     list.add(new ContainerFlow(match));
650                 }
651             }
652         }
653         return list;
654     }
655 }