blob: d5ef23293e4336c5c4ecca896da60446e44fab63 [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;
Madan Jampaniad3c5262016-01-20 00:50:17 -080038import org.onosproject.cluster.ClusterMetadata;
39import org.onosproject.cluster.ClusterMetadataProvider;
40import org.onosproject.cluster.ClusterMetadataProviderRegistry;
41import org.onosproject.cluster.ClusterMetadataProviderService;
Madan Jampaniad3c5262016-01-20 00:50:17 -080042import org.onosproject.cluster.DefaultControllerNode;
Jordan Halterman00e92da2018-05-22 23:05:52 -070043import org.onosproject.cluster.Node;
Madan Jampaniad3c5262016-01-20 00:50:17 -080044import org.onosproject.cluster.NodeId;
Madan Jampaniad3c5262016-01-20 00:50:17 -080045import org.onosproject.cluster.PartitionId;
46import org.onosproject.net.provider.ProviderId;
47import org.onosproject.store.service.Versioned;
48import org.slf4j.Logger;
49
Madan Jampaniad3c5262016-01-20 00:50:17 -080050import static com.google.common.base.Preconditions.checkState;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070051import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Ray Milkey6a51cb92018-03-06 09:03:03 -080052import static org.onlab.util.Tools.groupedThreads;
53import static org.slf4j.LoggerFactory.getLogger;
Madan Jampaniad3c5262016-01-20 00:50:17 -080054
55/**
56 * Provider of {@link ClusterMetadata cluster metadata} sourced from a local config file.
57 */
58@Component(immediate = true)
59public class ConfigFileBasedClusterMetadataProvider implements ClusterMetadataProvider {
60
61 private final Logger log = getLogger(getClass());
62
Madan Jampani898fcca2016-02-26 12:42:08 -080063 private static final String CONFIG_DIR = "../config";
64 private static final String CONFIG_FILE_NAME = "cluster.json";
65 private static final File CONFIG_FILE = new File(CONFIG_DIR, CONFIG_FILE_NAME);
Madan Jampaniad3c5262016-01-20 00:50:17 -080066
67 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
68 protected ClusterMetadataProviderRegistry providerRegistry;
69
Madan Jampanif172d402016-03-04 00:56:38 -080070 private static final ProviderId PROVIDER_ID = new ProviderId("file", "none");
Madan Jampani898fcca2016-02-26 12:42:08 -080071 private final AtomicReference<Versioned<ClusterMetadata>> cachedMetadata = new AtomicReference<>();
Madan Jampanif172d402016-03-04 00:56:38 -080072 private final ScheduledExecutorService configFileChangeDetector =
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070073 newSingleThreadScheduledExecutor(groupedThreads("onos/cluster/metadata/config-watcher", "", log));
Madan Jampaniad3c5262016-01-20 00:50:17 -080074
Madan Jampanif172d402016-03-04 00:56:38 -080075 private String metadataUrl;
Madan Jampaniad3c5262016-01-20 00:50:17 -080076 private ObjectMapper mapper;
77 private ClusterMetadataProviderService providerService;
78
79 @Activate
80 public void activate() {
81 mapper = new ObjectMapper();
Madan Jampaniad3c5262016-01-20 00:50:17 -080082 providerService = providerRegistry.register(this);
Madan Jampanif172d402016-03-04 00:56:38 -080083 metadataUrl = System.getProperty("onos.cluster.metadata.uri", "file://" + CONFIG_DIR + "/" + CONFIG_FILE);
84 configFileChangeDetector.scheduleWithFixedDelay(() -> watchUrl(metadataUrl), 100, 500, TimeUnit.MILLISECONDS);
Madan Jampaniad3c5262016-01-20 00:50:17 -080085 log.info("Started");
86 }
87
88 @Deactivate
89 public void deactivate() {
Madan Jampani898fcca2016-02-26 12:42:08 -080090 configFileChangeDetector.shutdown();
Madan Jampaniad3c5262016-01-20 00:50:17 -080091 providerRegistry.unregister(this);
92 log.info("Stopped");
93 }
94
95 @Override
96 public ProviderId id() {
97 return PROVIDER_ID;
98 }
99
100 @Override
101 public Versioned<ClusterMetadata> getClusterMetadata() {
102 checkState(isAvailable());
103 synchronized (this) {
104 if (cachedMetadata.get() == null) {
Jordan Haltermandbfff062017-06-19 10:31:32 -0700105 cachedMetadata.set(blockForMetadata(metadataUrl));
Madan Jampaniad3c5262016-01-20 00:50:17 -0800106 }
107 return cachedMetadata.get();
108 }
109 }
110
Jordan Halterman00e92da2018-05-22 23:05:52 -0700111 private ClusterMetadataPrototype toPrototype(ClusterMetadata metadata) {
112 ClusterMetadataPrototype prototype = new ClusterMetadataPrototype();
113 prototype.setName(metadata.getName());
Jordan Halterman19c123a2018-07-30 13:57:19 -0700114 prototype.setController(metadata.getNodes()
115 .stream()
116 .map(this::toPrototype)
117 .collect(Collectors.toSet()));
118 prototype.setStorage(metadata.getStorageNodes()
119 .stream()
120 .map(this::toPrototype)
121 .collect(Collectors.toSet()));
Jordan Halterman00e92da2018-05-22 23:05:52 -0700122 return prototype;
123 }
124
125 private NodePrototype toPrototype(Node node) {
126 NodePrototype prototype = new NodePrototype();
127 prototype.setId(node.id().id());
Jordan Haltermane458f002018-09-18 17:42:05 -0700128 prototype.setHost(node.host());
Jordan Halterman00e92da2018-05-22 23:05:52 -0700129 prototype.setPort(node.tcpPort());
130 return prototype;
131 }
132
Madan Jampaniad3c5262016-01-20 00:50:17 -0800133 @Override
134 public void setClusterMetadata(ClusterMetadata metadata) {
135 try {
Ray Milkey83200882017-11-13 19:18:21 -0800136 File configFile = new File(metadataUrl.replaceFirst("file://", ""));
137 Files.createParentDirs(configFile);
Jordan Halterman00e92da2018-05-22 23:05:52 -0700138 ClusterMetadataPrototype metadataPrototype = toPrototype(metadata);
139 mapper.writeValue(configFile, metadataPrototype);
Ray Milkey83200882017-11-13 19:18:21 -0800140 cachedMetadata.set(fetchMetadata(metadataUrl));
141 providerService.clusterMetadataChanged(new Versioned<>(metadata, configFile.lastModified()));
Madan Jampaniad3c5262016-01-20 00:50:17 -0800142 } catch (IOException e) {
Ray Milkey6a51cb92018-03-06 09:03:03 -0800143 throw new IllegalArgumentException(e);
Madan Jampaniad3c5262016-01-20 00:50:17 -0800144 }
145 }
146
147 @Override
148 public void addActivePartitionMember(PartitionId partitionId, NodeId nodeId) {
149 throw new UnsupportedOperationException();
150 }
151
152 @Override
153 public void removeActivePartitionMember(PartitionId partitionId, NodeId nodeId) {
154 throw new UnsupportedOperationException();
155 }
156
157 @Override
158 public Set<NodeId> getActivePartitionMembers(PartitionId partitionId) {
159 throw new UnsupportedOperationException();
160 }
161
162 @Override
163 public boolean isAvailable() {
Madan Jampanif172d402016-03-04 00:56:38 -0800164 try {
165 URL url = new URL(metadataUrl);
Jon Halla3fcf672017-03-28 16:53:22 -0700166 if ("file".equals(url.getProtocol())) {
Madan Jampanif172d402016-03-04 00:56:38 -0800167 File file = new File(metadataUrl.replaceFirst("file://", ""));
168 return file.exists();
Madan Jampanif172d402016-03-04 00:56:38 -0800169 } else {
Jordan Halterman46c76902017-08-24 14:55:25 -0700170 // Return true for HTTP URLs since we allow blocking until HTTP servers come up
171 return "http".equals(url.getProtocol());
Madan Jampanif172d402016-03-04 00:56:38 -0800172 }
173 } catch (Exception e) {
Jon Halldb2cf752016-06-28 16:24:45 -0700174 log.warn("Exception accessing metadata file at {}:", metadataUrl, e);
Madan Jampanif172d402016-03-04 00:56:38 -0800175 return false;
176 }
Madan Jampaniad3c5262016-01-20 00:50:17 -0800177 }
178
Jordan Haltermandbfff062017-06-19 10:31:32 -0700179 private Versioned<ClusterMetadata> blockForMetadata(String metadataUrl) {
Ray Milkey3717e602018-02-01 13:49:47 -0800180 long iterations = 0;
Jordan Haltermandbfff062017-06-19 10:31:32 -0700181 for (;;) {
Jordan Halterman9ada34a2017-10-23 16:31:50 -0700182 try {
183 Versioned<ClusterMetadata> metadata = fetchMetadata(metadataUrl);
184 if (metadata != null) {
185 return metadata;
186 }
187 } catch (Exception e) {
188 log.warn("Exception attempting to access metadata file at {}: {}", metadataUrl, e);
Jordan Haltermandbfff062017-06-19 10:31:32 -0700189 }
190
191 try {
Ray Milkey961b19f2018-02-05 13:47:55 -0800192 Thread.sleep((int) Math.pow(2, iterations < 7 ? ++iterations : iterations) * 10L);
Jordan Haltermandbfff062017-06-19 10:31:32 -0700193 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800194 Thread.currentThread().interrupt();
Ray Milkey6a51cb92018-03-06 09:03:03 -0800195 throw new IllegalStateException(e);
Jordan Haltermandbfff062017-06-19 10:31:32 -0700196 }
197 }
198 }
199
Jordan Haltermane458f002018-09-18 17:42:05 -0700200 private static NodeId getNodeId(NodePrototype node) {
201 if (node.getId() != null) {
202 return NodeId.nodeId(node.getId());
203 } else if (node.getHost() != null) {
204 return NodeId.nodeId(node.getHost());
205 } else if (node.getIp() != null) {
206 return NodeId.nodeId(node.getIp());
207 } else {
208 return NodeId.nodeId(UUID.randomUUID().toString());
209 }
210 }
211
212 private static String getNodeHost(NodePrototype node) {
213 if (node.getHost() != null) {
214 return node.getHost();
215 } else if (node.getIp() != null) {
216 return node.getIp();
217 } else {
218 throw new IllegalArgumentException(
219 "Malformed cluster configuration: No host specified for node " + node.getId());
220 }
221 }
222
223 private static int getNodePort(NodePrototype node) {
224 if (node.getPort() != null) {
225 return node.getPort();
226 }
227 return DefaultControllerNode.DEFAULT_PORT;
228 }
229
Madan Jampanif172d402016-03-04 00:56:38 -0800230 private Versioned<ClusterMetadata> fetchMetadata(String metadataUrl) {
Madan Jampaniad3c5262016-01-20 00:50:17 -0800231 try {
Madan Jampanif172d402016-03-04 00:56:38 -0800232 URL url = new URL(metadataUrl);
Jordan Halterman00e92da2018-05-22 23:05:52 -0700233 ClusterMetadataPrototype metadata = null;
Madan Jampanif172d402016-03-04 00:56:38 -0800234 long version = 0;
Jon Halla3fcf672017-03-28 16:53:22 -0700235 if ("file".equals(url.getProtocol())) {
Madan Jampanif172d402016-03-04 00:56:38 -0800236 File file = new File(metadataUrl.replaceFirst("file://", ""));
237 version = file.lastModified();
Jordan Halterman00e92da2018-05-22 23:05:52 -0700238 metadata = mapper.readValue(new FileInputStream(file), ClusterMetadataPrototype.class);
Jon Halla3fcf672017-03-28 16:53:22 -0700239 } else if ("http".equals(url.getProtocol())) {
Jordan Haltermanda268372017-09-19 11:18:08 -0700240 try {
241 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
242 if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
243 log.warn("Could not reach metadata URL {}. Retrying...", url);
244 return null;
245 }
246 if (conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
247 return null;
248 }
249 version = conn.getLastModified();
Jordan Halterman00e92da2018-05-22 23:05:52 -0700250 metadata = mapper.readValue(conn.getInputStream(), ClusterMetadataPrototype.class);
Jordan Haltermanda268372017-09-19 11:18:08 -0700251 } catch (IOException e) {
Jordan Halterman46c76902017-08-24 14:55:25 -0700252 log.warn("Could not reach metadata URL {}. Retrying...", url);
253 return null;
254 }
Madan Jampanif172d402016-03-04 00:56:38 -0800255 }
Jordan Haltermandbfff062017-06-19 10:31:32 -0700256
zhiyong ke60e40002017-04-13 13:50:47 +0800257 if (null == metadata) {
258 log.warn("Metadata is null in the function fetchMetadata");
259 throw new NullPointerException();
260 }
Jordan Haltermandbfff062017-06-19 10:31:32 -0700261
Jordan Halterman00e92da2018-05-22 23:05:52 -0700262 return new Versioned<>(new ClusterMetadata(
263 PROVIDER_ID,
264 metadata.getName(),
265 metadata.getNode() != null ?
266 new DefaultControllerNode(
Jordan Haltermane458f002018-09-18 17:42:05 -0700267 getNodeId(metadata.getNode()),
268 getNodeHost(metadata.getNode()),
269 getNodePort(metadata.getNode())) : null,
Jordan Halterman19c123a2018-07-30 13:57:19 -0700270 metadata.getController()
271 .stream()
Jordan Haltermane458f002018-09-18 17:42:05 -0700272 .map(node -> new DefaultControllerNode(getNodeId(node), getNodeHost(node), getNodePort(node)))
Jordan Halterman19c123a2018-07-30 13:57:19 -0700273 .collect(Collectors.toSet()),
274 metadata.getStorage()
275 .stream()
Jordan Haltermane458f002018-09-18 17:42:05 -0700276 .map(node -> new DefaultControllerNode(getNodeId(node), getNodeHost(node), getNodePort(node)))
Jordan Halterman19c123a2018-07-30 13:57:19 -0700277 .collect(Collectors.toSet())),
Jordan Halterman00e92da2018-05-22 23:05:52 -0700278 version);
Madan Jampaniad3c5262016-01-20 00:50:17 -0800279 } catch (IOException e) {
Ray Milkey6a51cb92018-03-06 09:03:03 -0800280 throw new IllegalArgumentException(e);
Madan Jampaniad3c5262016-01-20 00:50:17 -0800281 }
Madan Jampaniad3c5262016-01-20 00:50:17 -0800282 }
283
Madan Jampani898fcca2016-02-26 12:42:08 -0800284 /**
Madan Jampanif172d402016-03-04 00:56:38 -0800285 * Monitors the metadata url for any updates and notifies providerService accordingly.
Madan Jampani898fcca2016-02-26 12:42:08 -0800286 */
Madan Jampanif172d402016-03-04 00:56:38 -0800287 private void watchUrl(String metadataUrl) {
Ray Milkeye3708c72017-11-13 13:10:46 -0800288 if (!isAvailable()) {
289 return;
290 }
Madan Jampanif172d402016-03-04 00:56:38 -0800291 // TODO: We are merely polling the url.
292 // 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 +0530293 try {
294 Versioned<ClusterMetadata> latestMetadata = fetchMetadata(metadataUrl);
295 if (cachedMetadata.get() != null && latestMetadata != null
296 && cachedMetadata.get().version() < latestMetadata.version()) {
297 cachedMetadata.set(latestMetadata);
298 providerService.clusterMetadataChanged(latestMetadata);
299 }
300 } catch (Exception e) {
301 log.error("Unable to parse metadata : ", e);
Madan Jampani898fcca2016-02-26 12:42:08 -0800302 }
303 }
Jordan Halterman00e92da2018-05-22 23:05:52 -0700304
305 private static class ClusterMetadataPrototype {
306 private String name;
307 private NodePrototype node;
Jordan Halterman19c123a2018-07-30 13:57:19 -0700308 private Set<NodePrototype> controller = Sets.newHashSet();
309 private Set<NodePrototype> storage = Sets.newHashSet();
Jordan Halterman00e92da2018-05-22 23:05:52 -0700310
311 public String getName() {
312 return name;
313 }
314
315 public void setName(String name) {
316 this.name = name;
317 }
318
319 public NodePrototype getNode() {
320 return node;
321 }
322
323 public void setNode(NodePrototype node) {
324 this.node = node;
325 }
326
Jordan Halterman19c123a2018-07-30 13:57:19 -0700327 public Set<NodePrototype> getController() {
328 return controller;
Jordan Halterman00e92da2018-05-22 23:05:52 -0700329 }
330
Jordan Halterman19c123a2018-07-30 13:57:19 -0700331 public void setController(Set<NodePrototype> controller) {
332 this.controller = controller;
333 }
334
335 public Set<NodePrototype> getStorage() {
336 return storage;
337 }
338
339 public void setStorage(Set<NodePrototype> storage) {
340 this.storage = storage;
Jordan Halterman00e92da2018-05-22 23:05:52 -0700341 }
342 }
343
344 private static class NodePrototype {
345 private String id;
346 private String ip;
Jordan Haltermane458f002018-09-18 17:42:05 -0700347 private String host;
Jordan Halterman00e92da2018-05-22 23:05:52 -0700348 private Integer port;
349
350 public String getId() {
351 return id;
352 }
353
354 public void setId(String id) {
355 this.id = id;
356 }
357
358 public String getIp() {
359 return ip;
360 }
361
362 public void setIp(String ip) {
363 this.ip = ip;
364 }
365
Jordan Haltermane458f002018-09-18 17:42:05 -0700366 public String getHost() {
367 return host;
368 }
369
370 public void setHost(String host) {
371 this.host = host;
372 }
373
Jordan Halterman00e92da2018-05-22 23:05:52 -0700374 public Integer getPort() {
375 return port;
376 }
377
378 public void setPort(Integer port) {
379 this.port = port;
380 }
381 }
Madan Jampaniad3c5262016-01-20 00:50:17 -0800382}