blob: e93ce331a25b6d1b16a3dea6e5b069816cce8462 [file] [log] [blame]
Madan Jampaniad3c5262016-01-20 00:50:17 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Madan Jampaniad3c5262016-01-20 00:50:17 -08003 *
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 */
16package org.onosproject.cluster.impl;
17
Jordan Halterman00e92da2018-05-22 23:05:52 -070018import java.io.File;
19import java.io.FileInputStream;
20import java.io.IOException;
21import java.net.HttpURLConnection;
22import java.net.URL;
23import java.util.Set;
24import java.util.UUID;
25import java.util.concurrent.ScheduledExecutorService;
26import java.util.concurrent.TimeUnit;
27import java.util.concurrent.atomic.AtomicReference;
28import java.util.stream.Collectors;
29
Ray Milkey6a51cb92018-03-06 09:03:03 -080030import com.fasterxml.jackson.databind.ObjectMapper;
Jordan Halterman19c123a2018-07-30 13:57:19 -070031import com.google.common.collect.Sets;
Ray Milkey6a51cb92018-03-06 09:03:03 -080032import com.google.common.io.Files;
Madan Jampaniad3c5262016-01-20 00:50:17 -080033import org.apache.felix.scr.annotations.Activate;
34import org.apache.felix.scr.annotations.Component;
35import org.apache.felix.scr.annotations.Deactivate;
36import org.apache.felix.scr.annotations.Reference;
37import org.apache.felix.scr.annotations.ReferenceCardinality;
38import org.onlab.packet.IpAddress;
39import org.onosproject.cluster.ClusterMetadata;
40import org.onosproject.cluster.ClusterMetadataProvider;
41import org.onosproject.cluster.ClusterMetadataProviderRegistry;
42import org.onosproject.cluster.ClusterMetadataProviderService;
Madan Jampaniad3c5262016-01-20 00:50:17 -080043import org.onosproject.cluster.DefaultControllerNode;
Jordan Halterman00e92da2018-05-22 23:05:52 -070044import org.onosproject.cluster.Node;
Madan Jampaniad3c5262016-01-20 00:50:17 -080045import org.onosproject.cluster.NodeId;
Madan Jampaniad3c5262016-01-20 00:50:17 -080046import org.onosproject.cluster.PartitionId;
47import org.onosproject.net.provider.ProviderId;
48import org.onosproject.store.service.Versioned;
49import org.slf4j.Logger;
50
Madan Jampaniad3c5262016-01-20 00:50:17 -080051import static com.google.common.base.Preconditions.checkState;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070052import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Ray Milkey6a51cb92018-03-06 09:03:03 -080053import static org.onlab.util.Tools.groupedThreads;
54import static org.slf4j.LoggerFactory.getLogger;
Madan Jampaniad3c5262016-01-20 00:50:17 -080055
56/**
57 * Provider of {@link ClusterMetadata cluster metadata} sourced from a local config file.
58 */
59@Component(immediate = true)
60public class ConfigFileBasedClusterMetadataProvider implements ClusterMetadataProvider {
61
62 private final Logger log = getLogger(getClass());
63
Madan Jampani898fcca2016-02-26 12:42:08 -080064 private static final String CONFIG_DIR = "../config";
65 private static final String CONFIG_FILE_NAME = "cluster.json";
66 private static final File CONFIG_FILE = new File(CONFIG_DIR, CONFIG_FILE_NAME);
Madan Jampaniad3c5262016-01-20 00:50:17 -080067
68 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
69 protected ClusterMetadataProviderRegistry providerRegistry;
70
Madan Jampanif172d402016-03-04 00:56:38 -080071 private static final ProviderId PROVIDER_ID = new ProviderId("file", "none");
Madan Jampani898fcca2016-02-26 12:42:08 -080072 private final AtomicReference<Versioned<ClusterMetadata>> cachedMetadata = new AtomicReference<>();
Madan Jampanif172d402016-03-04 00:56:38 -080073 private final ScheduledExecutorService configFileChangeDetector =
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070074 newSingleThreadScheduledExecutor(groupedThreads("onos/cluster/metadata/config-watcher", "", log));
Madan Jampaniad3c5262016-01-20 00:50:17 -080075
Madan Jampanif172d402016-03-04 00:56:38 -080076 private String metadataUrl;
Madan Jampaniad3c5262016-01-20 00:50:17 -080077 private ObjectMapper mapper;
78 private ClusterMetadataProviderService providerService;
79
80 @Activate
81 public void activate() {
82 mapper = new ObjectMapper();
Madan Jampaniad3c5262016-01-20 00:50:17 -080083 providerService = providerRegistry.register(this);
Madan Jampanif172d402016-03-04 00:56:38 -080084 metadataUrl = System.getProperty("onos.cluster.metadata.uri", "file://" + CONFIG_DIR + "/" + CONFIG_FILE);
85 configFileChangeDetector.scheduleWithFixedDelay(() -> watchUrl(metadataUrl), 100, 500, TimeUnit.MILLISECONDS);
Madan Jampaniad3c5262016-01-20 00:50:17 -080086 log.info("Started");
87 }
88
89 @Deactivate
90 public void deactivate() {
Madan Jampani898fcca2016-02-26 12:42:08 -080091 configFileChangeDetector.shutdown();
Madan Jampaniad3c5262016-01-20 00:50:17 -080092 providerRegistry.unregister(this);
93 log.info("Stopped");
94 }
95
96 @Override
97 public ProviderId id() {
98 return PROVIDER_ID;
99 }
100
101 @Override
102 public Versioned<ClusterMetadata> getClusterMetadata() {
103 checkState(isAvailable());
104 synchronized (this) {
105 if (cachedMetadata.get() == null) {
Jordan Haltermandbfff062017-06-19 10:31:32 -0700106 cachedMetadata.set(blockForMetadata(metadataUrl));
Madan Jampaniad3c5262016-01-20 00:50:17 -0800107 }
108 return cachedMetadata.get();
109 }
110 }
111
Jordan Halterman00e92da2018-05-22 23:05:52 -0700112 private ClusterMetadataPrototype toPrototype(ClusterMetadata metadata) {
113 ClusterMetadataPrototype prototype = new ClusterMetadataPrototype();
114 prototype.setName(metadata.getName());
Jordan Halterman19c123a2018-07-30 13:57:19 -0700115 prototype.setController(metadata.getNodes()
116 .stream()
117 .map(this::toPrototype)
118 .collect(Collectors.toSet()));
119 prototype.setStorage(metadata.getStorageNodes()
120 .stream()
121 .map(this::toPrototype)
122 .collect(Collectors.toSet()));
Jordan Halterman00e92da2018-05-22 23:05:52 -0700123 return prototype;
124 }
125
126 private NodePrototype toPrototype(Node node) {
127 NodePrototype prototype = new NodePrototype();
128 prototype.setId(node.id().id());
129 prototype.setIp(node.ip().toString());
130 prototype.setPort(node.tcpPort());
131 return prototype;
132 }
133
Madan Jampaniad3c5262016-01-20 00:50:17 -0800134 @Override
135 public void setClusterMetadata(ClusterMetadata metadata) {
136 try {
Ray Milkey83200882017-11-13 19:18:21 -0800137 File configFile = new File(metadataUrl.replaceFirst("file://", ""));
138 Files.createParentDirs(configFile);
Jordan Halterman00e92da2018-05-22 23:05:52 -0700139 ClusterMetadataPrototype metadataPrototype = toPrototype(metadata);
140 mapper.writeValue(configFile, metadataPrototype);
Ray Milkey83200882017-11-13 19:18:21 -0800141 cachedMetadata.set(fetchMetadata(metadataUrl));
142 providerService.clusterMetadataChanged(new Versioned<>(metadata, configFile.lastModified()));
Madan Jampaniad3c5262016-01-20 00:50:17 -0800143 } catch (IOException e) {
Ray Milkey6a51cb92018-03-06 09:03:03 -0800144 throw new IllegalArgumentException(e);
Madan Jampaniad3c5262016-01-20 00:50:17 -0800145 }
146 }
147
148 @Override
149 public void addActivePartitionMember(PartitionId partitionId, NodeId nodeId) {
150 throw new UnsupportedOperationException();
151 }
152
153 @Override
154 public void removeActivePartitionMember(PartitionId partitionId, NodeId nodeId) {
155 throw new UnsupportedOperationException();
156 }
157
158 @Override
159 public Set<NodeId> getActivePartitionMembers(PartitionId partitionId) {
160 throw new UnsupportedOperationException();
161 }
162
163 @Override
164 public boolean isAvailable() {
Madan Jampanif172d402016-03-04 00:56:38 -0800165 try {
166 URL url = new URL(metadataUrl);
Jon Halla3fcf672017-03-28 16:53:22 -0700167 if ("file".equals(url.getProtocol())) {
Madan Jampanif172d402016-03-04 00:56:38 -0800168 File file = new File(metadataUrl.replaceFirst("file://", ""));
169 return file.exists();
Madan Jampanif172d402016-03-04 00:56:38 -0800170 } else {
Jordan Halterman46c76902017-08-24 14:55:25 -0700171 // Return true for HTTP URLs since we allow blocking until HTTP servers come up
172 return "http".equals(url.getProtocol());
Madan Jampanif172d402016-03-04 00:56:38 -0800173 }
174 } catch (Exception e) {
Jon Halldb2cf752016-06-28 16:24:45 -0700175 log.warn("Exception accessing metadata file at {}:", metadataUrl, e);
Madan Jampanif172d402016-03-04 00:56:38 -0800176 return false;
177 }
Madan Jampaniad3c5262016-01-20 00:50:17 -0800178 }
179
Jordan Haltermandbfff062017-06-19 10:31:32 -0700180 private Versioned<ClusterMetadata> blockForMetadata(String metadataUrl) {
Ray Milkey3717e602018-02-01 13:49:47 -0800181 long iterations = 0;
Jordan Haltermandbfff062017-06-19 10:31:32 -0700182 for (;;) {
Jordan Halterman9ada34a2017-10-23 16:31:50 -0700183 try {
184 Versioned<ClusterMetadata> metadata = fetchMetadata(metadataUrl);
185 if (metadata != null) {
186 return metadata;
187 }
188 } catch (Exception e) {
189 log.warn("Exception attempting to access metadata file at {}: {}", metadataUrl, e);
Jordan Haltermandbfff062017-06-19 10:31:32 -0700190 }
191
192 try {
Ray Milkey961b19f2018-02-05 13:47:55 -0800193 Thread.sleep((int) Math.pow(2, iterations < 7 ? ++iterations : iterations) * 10L);
Jordan Haltermandbfff062017-06-19 10:31:32 -0700194 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800195 Thread.currentThread().interrupt();
Ray Milkey6a51cb92018-03-06 09:03:03 -0800196 throw new IllegalStateException(e);
Jordan Haltermandbfff062017-06-19 10:31:32 -0700197 }
198 }
199 }
200
Madan Jampanif172d402016-03-04 00:56:38 -0800201 private Versioned<ClusterMetadata> fetchMetadata(String metadataUrl) {
Madan Jampaniad3c5262016-01-20 00:50:17 -0800202 try {
Madan Jampanif172d402016-03-04 00:56:38 -0800203 URL url = new URL(metadataUrl);
Jordan Halterman00e92da2018-05-22 23:05:52 -0700204 ClusterMetadataPrototype metadata = null;
Madan Jampanif172d402016-03-04 00:56:38 -0800205 long version = 0;
Jon Halla3fcf672017-03-28 16:53:22 -0700206 if ("file".equals(url.getProtocol())) {
Madan Jampanif172d402016-03-04 00:56:38 -0800207 File file = new File(metadataUrl.replaceFirst("file://", ""));
208 version = file.lastModified();
Jordan Halterman00e92da2018-05-22 23:05:52 -0700209 metadata = mapper.readValue(new FileInputStream(file), ClusterMetadataPrototype.class);
Jon Halla3fcf672017-03-28 16:53:22 -0700210 } else if ("http".equals(url.getProtocol())) {
Jordan Haltermanda268372017-09-19 11:18:08 -0700211 try {
212 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
213 if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
214 log.warn("Could not reach metadata URL {}. Retrying...", url);
215 return null;
216 }
217 if (conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
218 return null;
219 }
220 version = conn.getLastModified();
Jordan Halterman00e92da2018-05-22 23:05:52 -0700221 metadata = mapper.readValue(conn.getInputStream(), ClusterMetadataPrototype.class);
Jordan Haltermanda268372017-09-19 11:18:08 -0700222 } catch (IOException e) {
Jordan Halterman46c76902017-08-24 14:55:25 -0700223 log.warn("Could not reach metadata URL {}. Retrying...", url);
224 return null;
225 }
Madan Jampanif172d402016-03-04 00:56:38 -0800226 }
Jordan Haltermandbfff062017-06-19 10:31:32 -0700227
zhiyong ke60e40002017-04-13 13:50:47 +0800228 if (null == metadata) {
229 log.warn("Metadata is null in the function fetchMetadata");
230 throw new NullPointerException();
231 }
Jordan Haltermandbfff062017-06-19 10:31:32 -0700232
Jordan Halterman00e92da2018-05-22 23:05:52 -0700233 return new Versioned<>(new ClusterMetadata(
234 PROVIDER_ID,
235 metadata.getName(),
236 metadata.getNode() != null ?
237 new DefaultControllerNode(
238 metadata.getNode().getId() != null
239 ? NodeId.nodeId(metadata.getNode().getId())
240 : metadata.getNode().getIp() != null
241 ? NodeId.nodeId(IpAddress.valueOf(metadata.getNode().getIp()).toString())
242 : NodeId.nodeId(UUID.randomUUID().toString()),
243 metadata.getNode().getIp() != null
244 ? IpAddress.valueOf(metadata.getNode().getIp())
245 : null,
246 metadata.getNode().getPort() != null
247 ? metadata.getNode().getPort()
248 : DefaultControllerNode.DEFAULT_PORT) : null,
Jordan Halterman19c123a2018-07-30 13:57:19 -0700249 metadata.getController()
250 .stream()
251 .map(node -> new DefaultControllerNode(
252 NodeId.nodeId(node.getId()),
253 IpAddress.valueOf(node.getIp()),
254 node.getPort() != null ? node.getPort() : 5679))
255 .collect(Collectors.toSet()),
256 metadata.getStorage()
257 .stream()
258 .map(node -> new DefaultControllerNode(
259 NodeId.nodeId(node.getId()),
260 IpAddress.valueOf(node.getIp()),
261 node.getPort() != null ? node.getPort() : 5679))
262 .collect(Collectors.toSet())),
Jordan Halterman00e92da2018-05-22 23:05:52 -0700263 version);
Madan Jampaniad3c5262016-01-20 00:50:17 -0800264 } catch (IOException e) {
Ray Milkey6a51cb92018-03-06 09:03:03 -0800265 throw new IllegalArgumentException(e);
Madan Jampaniad3c5262016-01-20 00:50:17 -0800266 }
Madan Jampaniad3c5262016-01-20 00:50:17 -0800267 }
268
Madan Jampani898fcca2016-02-26 12:42:08 -0800269 /**
Madan Jampanif172d402016-03-04 00:56:38 -0800270 * Monitors the metadata url for any updates and notifies providerService accordingly.
Madan Jampani898fcca2016-02-26 12:42:08 -0800271 */
Madan Jampanif172d402016-03-04 00:56:38 -0800272 private void watchUrl(String metadataUrl) {
Ray Milkeye3708c72017-11-13 13:10:46 -0800273 if (!isAvailable()) {
274 return;
275 }
Madan Jampanif172d402016-03-04 00:56:38 -0800276 // TODO: We are merely polling the url.
277 // This can be easily addressed for files. For http urls we need to move to a push style protocol.
dvaddire49add802017-08-23 09:45:49 +0530278 try {
279 Versioned<ClusterMetadata> latestMetadata = fetchMetadata(metadataUrl);
280 if (cachedMetadata.get() != null && latestMetadata != null
281 && cachedMetadata.get().version() < latestMetadata.version()) {
282 cachedMetadata.set(latestMetadata);
283 providerService.clusterMetadataChanged(latestMetadata);
284 }
285 } catch (Exception e) {
286 log.error("Unable to parse metadata : ", e);
Madan Jampani898fcca2016-02-26 12:42:08 -0800287 }
288 }
Jordan Halterman00e92da2018-05-22 23:05:52 -0700289
290 private static class ClusterMetadataPrototype {
291 private String name;
292 private NodePrototype node;
Jordan Halterman19c123a2018-07-30 13:57:19 -0700293 private Set<NodePrototype> controller = Sets.newHashSet();
294 private Set<NodePrototype> storage = Sets.newHashSet();
Jordan Halterman00e92da2018-05-22 23:05:52 -0700295
296 public String getName() {
297 return name;
298 }
299
300 public void setName(String name) {
301 this.name = name;
302 }
303
304 public NodePrototype getNode() {
305 return node;
306 }
307
308 public void setNode(NodePrototype node) {
309 this.node = node;
310 }
311
Jordan Halterman19c123a2018-07-30 13:57:19 -0700312 public Set<NodePrototype> getController() {
313 return controller;
Jordan Halterman00e92da2018-05-22 23:05:52 -0700314 }
315
Jordan Halterman19c123a2018-07-30 13:57:19 -0700316 public void setController(Set<NodePrototype> controller) {
317 this.controller = controller;
318 }
319
320 public Set<NodePrototype> getStorage() {
321 return storage;
322 }
323
324 public void setStorage(Set<NodePrototype> storage) {
325 this.storage = storage;
Jordan Halterman00e92da2018-05-22 23:05:52 -0700326 }
327 }
328
329 private static class NodePrototype {
330 private String id;
331 private String ip;
332 private Integer port;
333
334 public String getId() {
335 return id;
336 }
337
338 public void setId(String id) {
339 this.id = id;
340 }
341
342 public String getIp() {
343 return ip;
344 }
345
346 public void setIp(String ip) {
347 this.ip = ip;
348 }
349
350 public Integer getPort() {
351 return port;
352 }
353
354 public void setPort(Integer port) {
355 this.port = port;
356 }
357 }
Madan Jampaniad3c5262016-01-20 00:50:17 -0800358}