Committed by
Gerrit Code Review
Turned netconf providers into an app and made NetconfDeviceProvider register its…
… configs with ComponentConfigService. More cleanup needs to be done to avoid log messages with "+" and some other stylistic issues. Change-Id: Ic1be1ce6d3340f5a6284ba5514d0052e01bdaaed
Showing
7 changed files
with
238 additions
and
146 deletions
... | @@ -130,10 +130,4 @@ | ... | @@ -130,10 +130,4 @@ |
130 | <bundle>mvn:org.onosproject/onos-core-trivial/@ONOS-VERSION</bundle> | 130 | <bundle>mvn:org.onosproject/onos-core-trivial/@ONOS-VERSION</bundle> |
131 | </feature> | 131 | </feature> |
132 | 132 | ||
133 | - <feature name="onos-netconf" version="@FEATURE-VERSION" | ||
134 | - description="ONOS Netconf providers"> | ||
135 | - <feature>onos-api</feature> | ||
136 | - <bundle>mvn:org.onosproject/onos-netconf-provider-device/@ONOS-VERSION</bundle> | ||
137 | - </feature> | ||
138 | - | ||
139 | </features> | 133 | </features> | ... | ... |
providers/netconf/app/app.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<!-- | ||
3 | + ~ Copyright 2015 Open Networking Laboratory | ||
4 | + ~ | ||
5 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + ~ you may not use this file except in compliance with the License. | ||
7 | + ~ 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, software | ||
12 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + ~ See the License for the specific language governing permissions and | ||
15 | + ~ limitations under the License. | ||
16 | + --> | ||
17 | +<app name="org.onosproject.netconf" origin="ON.Lab" version="${project.version}" | ||
18 | + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" | ||
19 | + features="${project.artifactId}"> | ||
20 | + <description>${project.description}</description> | ||
21 | + | ||
22 | + <artifact>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</artifact> | ||
23 | + <!-- Question: should there be the jnc stuff here? Or is it just for testing --> | ||
24 | +</app> |
providers/netconf/app/features.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
2 | +<!-- | ||
3 | + ~ Copyright 2015 Open Networking Laboratory | ||
4 | + ~ | ||
5 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + ~ you may not use this file except in compliance with the License. | ||
7 | + ~ 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, software | ||
12 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + ~ See the License for the specific language governing permissions and | ||
15 | + ~ limitations under the License. | ||
16 | + --> | ||
17 | +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> | ||
18 | + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> | ||
19 | + <feature name="${project.artifactId}" version="${project.version}" | ||
20 | + description="${project.description}"> | ||
21 | + <feature>onos-api</feature> | ||
22 | + <bundle>mvn:io.netty/netty/3.9.2.Final</bundle> | ||
23 | + <bundle>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</bundle> | ||
24 | + <!-- Question: should there be the jnc stuff here? Or is it just for testing --> | ||
25 | + </feature> | ||
26 | +</features> | ||
27 | + |
providers/netconf/app/pom.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<!-- | ||
3 | + ~ Copyright 2015 Open Networking Laboratory | ||
4 | + ~ | ||
5 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + ~ you may not use this file except in compliance with the License. | ||
7 | + ~ 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, software | ||
12 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + ~ See the License for the specific language governing permissions and | ||
15 | + ~ limitations under the License. | ||
16 | + --> | ||
17 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
18 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
20 | + <modelVersion>4.0.0</modelVersion> | ||
21 | + | ||
22 | + <parent> | ||
23 | + <groupId>org.onosproject</groupId> | ||
24 | + <artifactId>onos-netconf-providers</artifactId> | ||
25 | + <version>1.2.0-SNAPSHOT</version> | ||
26 | + <relativePath>../pom.xml</relativePath> | ||
27 | + </parent> | ||
28 | + | ||
29 | + <artifactId>onos-netconf</artifactId> | ||
30 | + <packaging>pom</packaging> | ||
31 | + | ||
32 | + <description>NetConf protocol southbound providers</description> | ||
33 | + | ||
34 | + <dependencies> | ||
35 | + <dependency> | ||
36 | + <groupId>org.onosproject</groupId> | ||
37 | + <artifactId>onos-netconf-provider-device</artifactId> | ||
38 | + <version>${project.version}</version> | ||
39 | + </dependency> | ||
40 | + <!-- TODO: add other dependencies here as more bundles are added to the app --> | ||
41 | + </dependencies> | ||
42 | + | ||
43 | +</project> |
... | @@ -14,134 +14,138 @@ | ... | @@ -14,134 +14,138 @@ |
14 | ~ See the License for the specific language governing permissions and | 14 | ~ See the License for the specific language governing permissions and |
15 | ~ limitations under the License. | 15 | ~ limitations under the License. |
16 | --> | 16 | --> |
17 | -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 17 | +<project xmlns="http://maven.apache.org/POM/4.0.0" |
18 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | 18 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
19 | - <modelVersion>4.0.0</modelVersion> | 19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
20 | + <modelVersion>4.0.0</modelVersion> | ||
20 | 21 | ||
21 | - <parent> | 22 | + <parent> |
22 | - <groupId>org.onosproject</groupId> | 23 | + <groupId>org.onosproject</groupId> |
23 | - <artifactId>onos-netconf-providers</artifactId> | 24 | + <artifactId>onos-netconf-providers</artifactId> |
24 | - <version>1.2.0-SNAPSHOT</version> | 25 | + <version>1.2.0-SNAPSHOT</version> |
25 | - <relativePath>../pom.xml</relativePath> | 26 | + <relativePath>../pom.xml</relativePath> |
26 | - </parent> | 27 | + </parent> |
28 | + | ||
29 | + <artifactId>onos-netconf-provider-device</artifactId> | ||
30 | + <packaging>bundle</packaging> | ||
27 | 31 | ||
28 | - <artifactId>onos-netconf-provider-device</artifactId> | 32 | + <description>ONOS Netconf protocol device provider</description> |
29 | - <packaging>bundle</packaging> | ||
30 | 33 | ||
31 | - <description>ONOS Netconf protocol device provider</description> | 34 | + <dependencies> |
32 | - <build> | 35 | + <dependency> |
33 | - <plugins> | 36 | + <groupId>org.osgi</groupId> |
34 | - <plugin> | 37 | + <artifactId>org.osgi.compendium</artifactId> |
35 | - <groupId>org.apache.maven.plugins</groupId> | 38 | + </dependency> |
36 | - <artifactId>maven-shade-plugin</artifactId> | 39 | + <dependency> |
37 | - <version>2.3</version> | 40 | + <groupId>ch.ethz.ganymed</groupId> |
38 | - <configuration> | 41 | + <artifactId>ganymed-ssh2</artifactId> |
39 | - <filters> | 42 | + <version>262</version> |
40 | - <filter> | 43 | + </dependency> |
41 | - <artifact>com.tailf:JNC</artifact> | 44 | + <dependency> |
42 | - <includes> | 45 | + <!-- TODO: change this appropriately when the official TailF JNC is available --> |
43 | - <include>com/tailf/jnc/**</include> | 46 | + <groupId>org.onosproject</groupId> |
44 | - </includes> | 47 | + <artifactId>jnc</artifactId> |
45 | - </filter> | 48 | + <version>1.0</version> |
46 | - <filter> | 49 | + </dependency> |
47 | - <artifact>ch.ethz.ganymed:ganymed-ssh2</artifact> | 50 | + <dependency> |
48 | - <includes> | 51 | + <groupId>org.jdom</groupId> |
49 | - <include>ch/ethz/ssh2/**</include> | 52 | + <artifactId>jdom2</artifactId> |
50 | - </includes> | 53 | + <version>2.0.5</version> |
51 | - </filter> | 54 | + </dependency> |
52 | - <filter> | 55 | + <dependency> |
53 | - <artifact>org.jdom:jdom2</artifact> | 56 | + <groupId>jaxen</groupId> |
54 | - <includes> | 57 | + <artifactId>jaxen</artifactId> |
55 | - <include>org/jdom2/**</include> | 58 | + <version>1.1.4</version> |
56 | - </includes> | 59 | + <optional>true</optional> |
57 | - </filter> | 60 | + </dependency> |
58 | - </filters> | 61 | + </dependencies> |
59 | - </configuration> | 62 | + |
60 | - <executions> | 63 | + <build> |
61 | - <execution> | 64 | + <plugins> |
62 | - <phase>package</phase> | 65 | + <plugin> |
63 | - <goals> | 66 | + <groupId>org.apache.maven.plugins</groupId> |
64 | - <goal>shade</goal> | 67 | + <artifactId>maven-shade-plugin</artifactId> |
65 | - </goals> | 68 | + <version>2.3</version> |
66 | - </execution> | 69 | + <configuration> |
67 | - </executions> | 70 | + <filters> |
68 | - </plugin> | 71 | + <filter> |
69 | - <plugin> | 72 | + <artifact>com.tailf:JNC</artifact> |
70 | - <groupId>org.apache.felix</groupId> | 73 | + <includes> |
71 | - <artifactId>maven-scr-plugin</artifactId> | 74 | + <include>com/tailf/jnc/**</include> |
72 | - </plugin> | 75 | + </includes> |
73 | - <plugin> | 76 | + </filter> |
74 | - <groupId>org.apache.felix</groupId> | 77 | + <filter> |
75 | - <artifactId>maven-bundle-plugin</artifactId> | 78 | + <artifact>ch.ethz.ganymed:ganymed-ssh2</artifact> |
76 | - <configuration> | 79 | + <includes> |
77 | - <instructions> | 80 | + <include>ch/ethz/ssh2/**</include> |
78 | - <Export-Package> | 81 | + </includes> |
79 | - com.tailf.jnc, | 82 | + </filter> |
80 | - ch.ethz.ssh2, | 83 | + <filter> |
81 | - ch.ethz.ssh2.auth, | 84 | + <artifact>org.jdom:jdom2</artifact> |
82 | - ch.ethz.ssh2.channel, | 85 | + <includes> |
83 | - ch.ethz.ssh2.crypto, | 86 | + <include>org/jdom2/**</include> |
84 | - ch.ethz.ssh2.crypto.cipher, | 87 | + </includes> |
85 | - ch.ethz.ssh2.crypto.dh, | 88 | + </filter> |
86 | - ch.ethz.ssh2.crypto.digest, | 89 | + </filters> |
87 | - ch.ethz.ssh2.log, | 90 | + </configuration> |
88 | - ch.ethz.ssh2.packets, | 91 | + <executions> |
89 | - ch.ethz.ssh2.server, | 92 | + <execution> |
90 | - ch.ethz.ssh2.sftp, | 93 | + <phase>package</phase> |
91 | - ch.ethz.ssh2.signature, | 94 | + <goals> |
92 | - ch.ethz.ssh2.transport, | 95 | + <goal>shade</goal> |
93 | - ch.ethz.ssh2.util, | 96 | + </goals> |
94 | - org.jdom2, | 97 | + </execution> |
95 | - org.jdom2.input, | 98 | + </executions> |
96 | - org.jdom2.output, | 99 | + </plugin> |
97 | - org.jdom2.adapters, | 100 | + <plugin> |
98 | - org.jdom2.filter, | 101 | + <groupId>org.apache.felix</groupId> |
99 | - org.jdom2.internal, | 102 | + <artifactId>maven-scr-plugin</artifactId> |
100 | - org.jdom2.located, | 103 | + </plugin> |
101 | - org.jdom2.transform, | 104 | + <plugin> |
102 | - org.jdom2.util, | 105 | + <groupId>org.apache.felix</groupId> |
103 | - org.jdom2.xpath, | 106 | + <artifactId>maven-bundle-plugin</artifactId> |
104 | - org.jdom2.input.sax, | 107 | + <configuration> |
105 | - org.jdom2.input.stax, | 108 | + <instructions> |
106 | - org.jdom2.output.support, | 109 | + <Export-Package> |
107 | - org.jdom2.xpath.jaxen, | 110 | + com.tailf.jnc, |
108 | - org.jdom2.xpath.util | 111 | + ch.ethz.ssh2, |
109 | - </Export-Package> | 112 | + ch.ethz.ssh2.auth, |
110 | - </instructions> | 113 | + ch.ethz.ssh2.channel, |
111 | - </configuration> | 114 | + ch.ethz.ssh2.crypto, |
112 | - </plugin> | 115 | + ch.ethz.ssh2.crypto.cipher, |
113 | - <plugin> | 116 | + ch.ethz.ssh2.crypto.dh, |
114 | - <groupId>org.onosproject</groupId> | 117 | + ch.ethz.ssh2.crypto.digest, |
115 | - <artifactId>onos-maven-plugin</artifactId> | 118 | + ch.ethz.ssh2.log, |
116 | - </plugin> | 119 | + ch.ethz.ssh2.packets, |
117 | - </plugins> | 120 | + ch.ethz.ssh2.server, |
118 | - </build> | 121 | + ch.ethz.ssh2.sftp, |
119 | - <dependencies> | 122 | + ch.ethz.ssh2.signature, |
120 | - <dependency> | 123 | + ch.ethz.ssh2.transport, |
121 | - <groupId>org.osgi</groupId> | 124 | + ch.ethz.ssh2.util, |
122 | - <artifactId>org.osgi.compendium</artifactId> | 125 | + org.jdom2, |
123 | - </dependency> | 126 | + org.jdom2.input, |
124 | - <dependency> | 127 | + org.jdom2.output, |
125 | - <groupId>ch.ethz.ganymed</groupId> | 128 | + org.jdom2.adapters, |
126 | - <artifactId>ganymed-ssh2</artifactId> | 129 | + org.jdom2.filter, |
127 | - <version>262</version> | 130 | + org.jdom2.internal, |
128 | - </dependency> | 131 | + org.jdom2.located, |
129 | - <dependency> | 132 | + org.jdom2.transform, |
130 | - <!-- TODO: change this appropriately when the official TailF JNC is available --> | 133 | + org.jdom2.util, |
134 | + org.jdom2.xpath, | ||
135 | + org.jdom2.input.sax, | ||
136 | + org.jdom2.input.stax, | ||
137 | + org.jdom2.output.support, | ||
138 | + org.jdom2.xpath.jaxen, | ||
139 | + org.jdom2.xpath.util | ||
140 | + </Export-Package> | ||
141 | + </instructions> | ||
142 | + </configuration> | ||
143 | + </plugin> | ||
144 | + <plugin> | ||
131 | <groupId>org.onosproject</groupId> | 145 | <groupId>org.onosproject</groupId> |
132 | - <artifactId>jnc</artifactId> | 146 | + <artifactId>onos-maven-plugin</artifactId> |
133 | - <version>1.0</version> | 147 | + </plugin> |
134 | - </dependency> | 148 | + </plugins> |
135 | - <dependency> | 149 | + </build> |
136 | - <groupId>org.jdom</groupId> | 150 | + |
137 | - <artifactId>jdom2</artifactId> | ||
138 | - <version>2.0.5</version> | ||
139 | - </dependency> | ||
140 | - <dependency> | ||
141 | - <groupId>jaxen</groupId> | ||
142 | - <artifactId>jaxen</artifactId> | ||
143 | - <version>1.1.4</version> | ||
144 | - <optional>true</optional> | ||
145 | - </dependency> | ||
146 | - </dependencies> | ||
147 | </project> | 151 | </project> | ... | ... |
... | @@ -39,6 +39,7 @@ import org.apache.felix.scr.annotations.Property; | ... | @@ -39,6 +39,7 @@ import org.apache.felix.scr.annotations.Property; |
39 | import org.apache.felix.scr.annotations.Reference; | 39 | import org.apache.felix.scr.annotations.Reference; |
40 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 40 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
41 | import org.onlab.packet.ChassisId; | 41 | import org.onlab.packet.ChassisId; |
42 | +import org.onosproject.cfg.ComponentConfigService; | ||
42 | import org.onosproject.cluster.ClusterService; | 43 | import org.onosproject.cluster.ClusterService; |
43 | import org.onosproject.net.Device; | 44 | import org.onosproject.net.Device; |
44 | import org.onosproject.net.DeviceId; | 45 | import org.onosproject.net.DeviceId; |
... | @@ -78,9 +79,11 @@ public class NetconfDeviceProvider extends AbstractProvider | ... | @@ -78,9 +79,11 @@ public class NetconfDeviceProvider extends AbstractProvider |
78 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 79 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
79 | protected ClusterService clusterService; | 80 | protected ClusterService clusterService; |
80 | 81 | ||
82 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
83 | + protected ComponentConfigService cfgService; | ||
84 | + | ||
81 | private ExecutorService deviceBuilder = Executors | 85 | private ExecutorService deviceBuilder = Executors |
82 | - .newFixedThreadPool(1, | 86 | + .newFixedThreadPool(1, groupedThreads("onos/netconf", "device-creator")); |
83 | - groupedThreads("onos/netconf", "device-creator")); | ||
84 | 87 | ||
85 | // Delay between events in ms. | 88 | // Delay between events in ms. |
86 | private static final int EVENTINTERVAL = 5; | 89 | private static final int EVENTINTERVAL = 5; |
... | @@ -90,7 +93,7 @@ public class NetconfDeviceProvider extends AbstractProvider | ... | @@ -90,7 +93,7 @@ public class NetconfDeviceProvider extends AbstractProvider |
90 | @Property(name = "devConfigs", value = "", label = "Instance-specific configurations") | 93 | @Property(name = "devConfigs", value = "", label = "Instance-specific configurations") |
91 | private String devConfigs = null; | 94 | private String devConfigs = null; |
92 | 95 | ||
93 | - @Property(name = "devPasswords", value = "", label = "Instace-specific password") | 96 | + @Property(name = "devPasswords", value = "", label = "Instance-specific password") |
94 | private String devPasswords = null; | 97 | private String devPasswords = null; |
95 | 98 | ||
96 | /** | 99 | /** |
... | @@ -102,13 +105,15 @@ public class NetconfDeviceProvider extends AbstractProvider | ... | @@ -102,13 +105,15 @@ public class NetconfDeviceProvider extends AbstractProvider |
102 | 105 | ||
103 | @Activate | 106 | @Activate |
104 | public void activate(ComponentContext context) { | 107 | public void activate(ComponentContext context) { |
105 | - log.info("Netconf Device Provider Started"); | 108 | + cfgService.registerProperties(getClass()); |
106 | providerService = providerRegistry.register(this); | 109 | providerService = providerRegistry.register(this); |
107 | modified(context); | 110 | modified(context); |
111 | + log.info("Started"); | ||
108 | } | 112 | } |
109 | 113 | ||
110 | @Deactivate | 114 | @Deactivate |
111 | public void deactivate(ComponentContext context) { | 115 | public void deactivate(ComponentContext context) { |
116 | + cfgService.unregisterProperties(getClass(), false); | ||
112 | try { | 117 | try { |
113 | for (Entry<DeviceId, NetconfDevice> deviceEntry : netconfDeviceMap | 118 | for (Entry<DeviceId, NetconfDevice> deviceEntry : netconfDeviceMap |
114 | .entrySet()) { | 119 | .entrySet()) { |
... | @@ -134,13 +139,9 @@ public class NetconfDeviceProvider extends AbstractProvider | ... | @@ -134,13 +139,9 @@ public class NetconfDeviceProvider extends AbstractProvider |
134 | } | 139 | } |
135 | Dictionary<?, ?> properties = context.getProperties(); | 140 | Dictionary<?, ?> properties = context.getProperties(); |
136 | String deviceCfgValue = get(properties, "devConfigs"); | 141 | String deviceCfgValue = get(properties, "devConfigs"); |
137 | - log.info("Getting Device configuration from cfg file: " | 142 | + log.info("Settings: devConfigs={}", deviceCfgValue); |
138 | - + deviceCfgValue); | ||
139 | if (!isNullOrEmpty(deviceCfgValue)) { | 143 | if (!isNullOrEmpty(deviceCfgValue)) { |
140 | addOrRemoveDevicesConfig(deviceCfgValue); | 144 | addOrRemoveDevicesConfig(deviceCfgValue); |
141 | - } else { | ||
142 | - log.info("Device Configuration value receiviced from the property 'devConfigs': " | ||
143 | - + deviceCfgValue + ", is not valid"); | ||
144 | } | 145 | } |
145 | } | 146 | } |
146 | 147 | ||
... | @@ -148,11 +149,9 @@ public class NetconfDeviceProvider extends AbstractProvider | ... | @@ -148,11 +149,9 @@ public class NetconfDeviceProvider extends AbstractProvider |
148 | for (String deviceEntry : deviceConfig.split(",")) { | 149 | for (String deviceEntry : deviceConfig.split(",")) { |
149 | NetconfDevice device = processDeviceEntry(deviceEntry); | 150 | NetconfDevice device = processDeviceEntry(deviceEntry); |
150 | if (device != null) { | 151 | if (device != null) { |
151 | - log.info("Device Detail: " + "username: " | 152 | + log.info("Device Detail: username: {}, host={}, port={}, state={}", |
152 | - + device.getUsername() + ", host: " | 153 | + device.getUsername(), device.getSshHost(), |
153 | - + device.getSshHost() + ", port: " | 154 | + device.getSshPort(), device.getDeviceState().name()); |
154 | - + device.getSshPort() + " device state: " | ||
155 | - + device.getDeviceState().name()); | ||
156 | if (device.isActive()) { | 155 | if (device.isActive()) { |
157 | deviceBuilder.submit(new DeviceCreator(device, true)); | 156 | deviceBuilder.submit(new DeviceCreator(device, true)); |
158 | } else { | 157 | } else { | ... | ... |
-
Please register or login to post a comment