YuanyouZhang
Committed by Gerrit Code Review

[ONOS-2393]--OVSDB- The implementation of the netty controller.

Change-Id: Ib24f5e6eb242a977ff6a1debdc22dcc09ff205c9
1 <?xml version="1.0"?> 1 <?xml version="1.0"?>
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 + -->
2 <project 17 <project
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" 18 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 19 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
......
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 + <parent>
22 + <groupId>org.onosproject</groupId>
23 + <artifactId>onos-ovsdb</artifactId>
24 + <version>1.3.0-SNAPSHOT</version>
25 + <relativePath>../pom.xml</relativePath>
26 + </parent>
27 +
28 + <artifactId>onos-ovsdb-ctl</artifactId>
29 + <packaging>bundle</packaging>
30 +
31 + <dependencies>
32 + <dependency>
33 + <groupId>junit</groupId>
34 + <artifactId>junit</artifactId>
35 + <scope>test</scope>
36 + </dependency>
37 + <dependency>
38 + <groupId>org.apache.felix</groupId>
39 + <artifactId>org.apache.felix.scr.annotations</artifactId>
40 + </dependency>
41 + <dependency>
42 + <groupId>org.osgi</groupId>
43 + <artifactId>org.osgi.compendium</artifactId>
44 + </dependency>
45 + <dependency>
46 + <groupId>org.onosproject</groupId>
47 + <artifactId>onos-ovsdb-api</artifactId>
48 + <version>${project.version}</version>
49 + </dependency>
50 + <dependency>
51 + <groupId>org.onosproject</groupId>
52 + <artifactId>onos-ovsdb-rfc</artifactId>
53 + <version>${project.version}</version>
54 + </dependency>
55 + </dependencies>
56 +
57 + <build>
58 + <plugins>
59 + <plugin>
60 + <groupId>org.apache.felix</groupId>
61 + <artifactId>maven-scr-plugin</artifactId>
62 + </plugin>
63 + </plugins>
64 + </build>
65 +</project>
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 +package org.onosproject.ovsdb.controller.impl;
17 +
18 +import io.netty.channel.ChannelFuture;
19 +import io.netty.channel.ChannelFutureListener;
20 +
21 +import org.onosproject.ovsdb.controller.driver.OvsdbProviderService;
22 +
23 +/**
24 + * The listener class. Handles when the node disconnect.
25 + */
26 +public class ChannelConnectionListener implements ChannelFutureListener {
27 +
28 + private final OvsdbProviderService providerService;
29 +
30 + /**
31 + * Constructor from a OvsdbProviderService providerService.
32 + *
33 + * @param providerService the providerService to use
34 + */
35 + public ChannelConnectionListener(OvsdbProviderService providerService) {
36 + this.providerService = providerService;
37 + }
38 +
39 + @Override
40 + public void operationComplete(ChannelFuture arg0) {
41 + providerService.nodeRemoved();
42 + }
43 +}
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 +package org.onosproject.ovsdb.controller.impl;
17 +
18 +import io.netty.bootstrap.ServerBootstrap;
19 +import io.netty.buffer.PooledByteBufAllocator;
20 +import io.netty.channel.Channel;
21 +import io.netty.channel.ChannelFuture;
22 +import io.netty.channel.ChannelInitializer;
23 +import io.netty.channel.ChannelOption;
24 +import io.netty.channel.EventLoopGroup;
25 +import io.netty.channel.ServerChannel;
26 +import io.netty.channel.nio.NioEventLoopGroup;
27 +import io.netty.channel.socket.SocketChannel;
28 +import io.netty.channel.socket.nio.NioServerSocketChannel;
29 +import io.netty.handler.codec.string.StringEncoder;
30 +import io.netty.util.CharsetUtil;
31 +
32 +import java.net.InetSocketAddress;
33 +import java.util.concurrent.ExecutorService;
34 +import java.util.concurrent.Executors;
35 +
36 +import org.onlab.packet.IpAddress;
37 +import org.onosproject.ovsdb.controller.OvsdbConstant;
38 +import org.onosproject.ovsdb.controller.OvsdbNodeId;
39 +import org.onosproject.ovsdb.controller.driver.DefaultOvsdbClient;
40 +import org.onosproject.ovsdb.controller.driver.OvsdbAgent;
41 +import org.onosproject.ovsdb.controller.driver.OvsdbProviderService;
42 +import org.onosproject.ovsdb.rfc.jsonrpc.Callback;
43 +import org.slf4j.Logger;
44 +import org.slf4j.LoggerFactory;
45 +
46 +/**
47 + * The main controller class. Handles all setup and network listeners -
48 + * Distributed ovsdbClient.
49 + */
50 +public class Controller {
51 + protected static final Logger log = LoggerFactory
52 + .getLogger(Controller.class);
53 +
54 + private int ovsdbPort = OvsdbConstant.OVSDBPORT;
55 +
56 + private OvsdbAgent agent;
57 + private Callback monitorCallback;
58 +
59 + private final ExecutorService executorService = Executors
60 + .newFixedThreadPool(10);
61 +
62 + private EventLoopGroup bossGroup;
63 + private EventLoopGroup workerGroup;
64 + private Class<? extends ServerChannel> serverChannelClass;
65 +
66 + /**
67 + * Initialization.
68 + */
69 + private void initEventLoopGroup() {
70 + bossGroup = new NioEventLoopGroup();
71 + workerGroup = new NioEventLoopGroup();
72 + serverChannelClass = NioServerSocketChannel.class;
73 + }
74 +
75 + /**
76 + * Accepts incoming connections.
77 + */
78 + private void startAcceptingConnections() throws InterruptedException {
79 + ServerBootstrap b = new ServerBootstrap();
80 +
81 + b.group(bossGroup, workerGroup).channel(serverChannelClass)
82 + .childHandler(new OnosCommunicationChannelInitializer());
83 + b.option(ChannelOption.SO_BACKLOG, 128);
84 + b.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024);
85 + b.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);
86 + b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
87 + b.childOption(ChannelOption.SO_KEEPALIVE, true);
88 + ChannelFuture cf = b.bind(ovsdbPort).sync();
89 + cf.channel().closeFuture().sync();
90 + }
91 +
92 + /**
93 + * Tells controller that we're ready to accept ovsdb node loop.
94 + */
95 + public void run() throws InterruptedException {
96 + initEventLoopGroup();
97 + startAcceptingConnections();
98 + }
99 +
100 + /**
101 + * Adds channel pipiline to handle a new connected node.
102 + */
103 + private class OnosCommunicationChannelInitializer
104 + extends ChannelInitializer<SocketChannel> {
105 + protected void initChannel(SocketChannel channel) throws Exception {
106 + log.info("New channel created");
107 + channel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
108 + channel.pipeline().addLast(new MessageDecoder());
109 + handleNewNodeConnection(channel);
110 +
111 + }
112 + }
113 +
114 + /**
115 + * Handles the new connection of a node.
116 + *
117 + * @param channel the channel to use.
118 + */
119 + private void handleNewNodeConnection(final Channel channel) {
120 + executorService.execute(new Runnable() {
121 + @Override
122 + public void run() {
123 + log.info("Handle new node connection");
124 +
125 + IpAddress ipAddress = IpAddress
126 + .valueOf(((InetSocketAddress) channel.remoteAddress())
127 + .getAddress().getHostAddress());
128 + long port = ((InetSocketAddress) channel.remoteAddress())
129 + .getPort();
130 +
131 + log.info("Get connection from ip address {} : {}",
132 + ipAddress.toString(), port);
133 +
134 + OvsdbNodeId nodeId = new OvsdbNodeId(ipAddress, port);
135 + OvsdbProviderService ovsdbProviderService = getNodeInstance(nodeId,
136 + agent,
137 + monitorCallback,
138 + channel);
139 + ovsdbProviderService.setConnection(true);
140 + OvsdbJsonRpcHandler ovsdbJsonRpcHandler = new OvsdbJsonRpcHandler(
141 + nodeId);
142 + ovsdbJsonRpcHandler
143 + .setOvsdbProviderService(ovsdbProviderService);
144 + channel.pipeline().addLast(ovsdbJsonRpcHandler);
145 +
146 + ovsdbProviderService.nodeAdded();
147 + ChannelFuture closeFuture = channel.closeFuture();
148 + closeFuture
149 + .addListener(new ChannelConnectionListener(
150 + ovsdbProviderService));
151 + }
152 + });
153 + }
154 +
155 + /**
156 + * Gets an ovsdb client instance.
157 + *
158 + * @param nodeId data ovsdb node id
159 + * @param agent OvsdbAgent
160 + * @param monitorCallback Callback
161 + * @param channel Channel
162 + * @return OvsdbProviderService instance
163 + */
164 + protected OvsdbProviderService getNodeInstance(OvsdbNodeId nodeId,
165 + OvsdbAgent agent,
166 + Callback monitorCallback,
167 + Channel channel) {
168 + OvsdbProviderService ovsdbProviderService = new DefaultOvsdbClient(
169 + nodeId);
170 + ovsdbProviderService.setAgent(agent);
171 + ovsdbProviderService.setCallback(monitorCallback);
172 + ovsdbProviderService.setChannel(channel);
173 + return ovsdbProviderService;
174 + }
175 +
176 + /**
177 + * Starts controller.
178 + *
179 + * @param agent OvsdbAgent
180 + * @param monitorCallback Callback
181 + */
182 + public void start(OvsdbAgent agent, Callback monitorCallback) {
183 + this.agent = agent;
184 + this.monitorCallback = monitorCallback;
185 + try {
186 + this.run();
187 + } catch (InterruptedException e) {
188 + log.warn("Interrupted while waiting to start");
189 + Thread.currentThread().interrupt();
190 + }
191 + }
192 +
193 + /**
194 + * Stops controller.
195 + *
196 + */
197 + public void stop() {
198 + workerGroup.shutdownGracefully();
199 + bossGroup.shutdownGracefully();
200 + }
201 +}
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 +package org.onosproject.ovsdb.controller.impl;
17 +
18 +import io.netty.buffer.ByteBuf;
19 +import io.netty.channel.ChannelHandlerContext;
20 +import io.netty.handler.codec.ByteToMessageDecoder;
21 +
22 +import java.util.List;
23 +
24 +import org.onosproject.ovsdb.rfc.jsonrpc.JsonReadContext;
25 +import org.onosproject.ovsdb.rfc.utils.JsonRpcReaderUtil;
26 +import org.slf4j.Logger;
27 +import org.slf4j.LoggerFactory;
28 +
29 +/**
30 + * Decoder for inbound messages.
31 + */
32 +public class MessageDecoder extends ByteToMessageDecoder {
33 +
34 + private final Logger log = LoggerFactory.getLogger(MessageDecoder.class);
35 + private final JsonReadContext context = new JsonReadContext();
36 +
37 + /**
38 + * Default constructor.
39 + */
40 + public MessageDecoder() {
41 + }
42 +
43 + @Override
44 + protected void decode(ChannelHandlerContext ctx, ByteBuf buf,
45 + List<Object> out) throws Exception {
46 + log.debug("Message decoder");
47 + JsonRpcReaderUtil.readToJsonNode(buf, out, context);
48 + }
49 +
50 + @Override
51 + public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
52 + log.error("Exception inside channel handling pipeline.", cause);
53 + context.close();
54 + }
55 +}
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 +package org.onosproject.ovsdb.controller.impl;
17 +
18 +import io.netty.channel.ChannelHandlerContext;
19 +import io.netty.channel.ChannelInboundHandlerAdapter;
20 +
21 +import org.onosproject.ovsdb.controller.OvsdbNodeId;
22 +import org.onosproject.ovsdb.controller.driver.OvsdbProviderService;
23 +import org.slf4j.Logger;
24 +import org.slf4j.LoggerFactory;
25 +
26 +import com.fasterxml.jackson.databind.JsonNode;
27 +import com.google.common.base.Strings;
28 +
29 +/**
30 + * Channel handler deals with the node connection and dispatches
31 + * ovsdb messages to the appropriate locations.
32 + */
33 +public final class OvsdbJsonRpcHandler extends ChannelInboundHandlerAdapter {
34 + protected static final Logger log = LoggerFactory
35 + .getLogger(OvsdbJsonRpcHandler.class);
36 + private OvsdbNodeId ovsdbNodeId;
37 + private OvsdbProviderService ovsdbProviderService;
38 +
39 + /**
40 + * Constructor from a OvsdbNodeId ovsdbNodeId.
41 + *
42 + * @param ovsdbNodeId the ovsdbNodeId to use
43 + */
44 + public OvsdbJsonRpcHandler(OvsdbNodeId ovsdbNodeId) {
45 + super();
46 + this.ovsdbNodeId = ovsdbNodeId;
47 + }
48 +
49 + /**
50 + * Gets the ovsdbProviderService instance.
51 + *
52 + * @return the instance of the ovsdbProviderService
53 + */
54 + public OvsdbProviderService getOvsdbProviderService() {
55 + return ovsdbProviderService;
56 + }
57 +
58 + /**
59 + * Sets the ovsdbProviderService instance.
60 + *
61 + * @param ovsdbNodeDriver the ovsdbNodeDriver to use
62 + */
63 + public void setOvsdbProviderService(OvsdbProviderService ovsdbNodeDriver) {
64 + this.ovsdbProviderService = ovsdbNodeDriver;
65 + }
66 +
67 + /**
68 + * Gets the OvsdbNodeId instance.
69 + *
70 + * @return the instance of the OvsdbNodeId
71 + */
72 + public OvsdbNodeId getNodeId() {
73 + return ovsdbNodeId;
74 + }
75 +
76 + /**
77 + * Sets the ovsdb node id.
78 + *
79 + * @param ovsdbNodeId the ovsdbNodeId to use
80 + */
81 + public void setNodeId(OvsdbNodeId ovsdbNodeId) {
82 + this.ovsdbNodeId = ovsdbNodeId;
83 + }
84 +
85 + /**
86 + * Processes an JsonNode message received on the channel.
87 + *
88 + * @param jsonNode The OvsdbJsonRpcHandler that received the message
89 + */
90 + private void processOvsdbMessage(JsonNode jsonNode) {
91 +
92 + log.info("Handle ovsdb message");
93 +
94 + if (jsonNode.has("result")) {
95 +
96 + log.debug("Handle ovsdb result");
97 + ovsdbProviderService.processResult(jsonNode);
98 +
99 + } else if (jsonNode.hasNonNull("method")) {
100 +
101 + log.debug("Handle ovsdb request");
102 + if (jsonNode.has("id")
103 + && !Strings.isNullOrEmpty(jsonNode.get("id").asText())) {
104 + ovsdbProviderService.processRequest(jsonNode);
105 + }
106 +
107 + }
108 + return;
109 + }
110 +
111 + @Override
112 + public void channelRead(ChannelHandlerContext ctx, Object msg)
113 + throws Exception {
114 + log.debug("Receive message from ovsdb");
115 + if (msg instanceof JsonNode) {
116 + JsonNode jsonNode = (JsonNode) msg;
117 + processOvsdbMessage(jsonNode);
118 + }
119 + }
120 +
121 + @Override
122 + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
123 + ctx.flush();
124 + }
125 +
126 + @Override
127 + public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
128 + log.error("Exception inside channel handling pipeline.", cause);
129 + context.close();
130 + }
131 +}
1 -<?xml version="1.0" encoding="UTF-8"?> 1 +<?xml version="1.0" encoding="UTF-8"?>
2 -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 +<!--
3 - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 3 + ~ Copyright 2015 Open Networking Laboratory
4 - <modelVersion>4.0.0</modelVersion> 4 + ~
5 - <parent> 5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 - <groupId>org.onosproject</groupId> 6 + ~ you may not use this file except in compliance with the License.
7 - <artifactId>onos</artifactId> 7 + ~ You may obtain a copy of the License at
8 - <version>1.3.0-SNAPSHOT</version> 8 + ~
9 - </parent> 9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 - <artifactId>onos-ovsdb</artifactId> 10 + ~
11 - <name>onos-ovsdb</name> 11 + ~ Unless required by applicable law or agreed to in writing, software
12 - <packaging>pom</packaging> 12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 - 13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 - <description>ONOS OVSDB southbound plugin</description> 14 + ~ See the License for the specific language governing permissions and
15 - <dependencies> 15 + ~ limitations under the License.
16 - <dependency> 16 + -->
17 - <groupId>junit</groupId> 17 +<project xmlns="http://maven.apache.org/POM/4.0.0"
18 - <artifactId>junit</artifactId> 18 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19 - <scope>test</scope> 19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
20 - </dependency> 20 + <modelVersion>4.0.0</modelVersion>
21 - <dependency> 21 + <parent>
22 - <groupId>org.onosproject</groupId> 22 + <groupId>org.onosproject</groupId>
23 - <artifactId>onlab-misc</artifactId> 23 + <artifactId>onos</artifactId>
24 - </dependency> 24 + <version>1.3.0-SNAPSHOT</version>
25 - <dependency> 25 + </parent>
26 - <groupId>org.onosproject</groupId> 26 + <artifactId>onos-ovsdb</artifactId>
27 - <artifactId>onlab-junit</artifactId> 27 + <name>onos-ovsdb</name>
28 - </dependency> 28 + <packaging>pom</packaging>
29 - <dependency> 29 +
30 - <groupId>io.netty</groupId> 30 + <description>ONOS OVSDB southbound plugin</description>
31 - <artifactId>netty-buffer</artifactId> 31 + <dependencies>
32 - </dependency> 32 + <dependency>
33 - <dependency> 33 + <groupId>junit</groupId>
34 - <groupId>io.netty</groupId> 34 + <artifactId>junit</artifactId>
35 - <artifactId>netty-handler</artifactId> 35 + <scope>test</scope>
36 - </dependency> 36 + </dependency>
37 - </dependencies> 37 + <dependency>
38 - 38 + <groupId>org.onosproject</groupId>
39 - <build> 39 + <artifactId>onlab-misc</artifactId>
40 - <plugins> 40 + </dependency>
41 - <plugin> 41 + <dependency>
42 - <groupId>org.apache.felix</groupId> 42 + <groupId>org.onosproject</groupId>
43 - <artifactId>maven-bundle-plugin</artifactId> 43 + <artifactId>onlab-junit</artifactId>
44 - </plugin> 44 + </dependency>
45 - </plugins> 45 + <dependency>
46 - </build> 46 + <groupId>io.netty</groupId>
47 - 47 + <artifactId>netty-buffer</artifactId>
48 - <modules> 48 + </dependency>
49 - <module>api</module> 49 + <dependency>
50 - <module>rfc</module> 50 + <groupId>io.netty</groupId>
51 - </modules> 51 + <artifactId>netty-handler</artifactId>
52 -</project> 52 + </dependency>
53 + </dependencies>
54 +
55 + <build>
56 + <plugins>
57 + <plugin>
58 + <groupId>org.apache.felix</groupId>
59 + <artifactId>maven-bundle-plugin</artifactId>
60 + </plugin>
61 + </plugins>
62 + </build>
63 +
64 + <modules>
65 + <module>api</module>
66 + <module>rfc</module>
67 + <module>ctl</module>
68 + </modules>
69 +</project>
...\ No newline at end of file ...\ No newline at end of file
......