Open Kilda Java Documentation
TraffExamServiceImpl.java
Go to the documentation of this file.
1 /* Copyright 2018 Telstra Open Source
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package org.openkilda.testing.service.traffexam;
17 
18 import static java.util.Collections.unmodifiableMap;
19 
40 
41 import net.jodah.failsafe.Failsafe;
42 import net.jodah.failsafe.RetryPolicy;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.springframework.beans.factory.DisposableBean;
46 import org.springframework.beans.factory.annotation.Autowired;
47 import org.springframework.beans.factory.annotation.Qualifier;
48 import org.springframework.http.HttpStatus;
49 import org.springframework.stereotype.Service;
50 import org.springframework.web.client.HttpStatusCodeException;
51 import org.springframework.web.client.RestClientException;
52 import org.springframework.web.client.RestTemplate;
53 import org.springframework.web.util.UriBuilder;
54 import org.springframework.web.util.UriComponentsBuilder;
55 
56 import java.net.Inet4Address;
57 import java.net.URI;
58 import java.net.URISyntaxException;
59 import java.net.UnknownHostException;
60 import java.util.ArrayList;
61 import java.util.HashMap;
62 import java.util.InputMismatchException;
63 import java.util.LinkedList;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.UUID;
67 import java.util.concurrent.TimeUnit;
68 import javax.annotation.PostConstruct;
69 
70 @Service
71 public class TraffExamServiceImpl implements TraffExamService, DisposableBean {
72 
73  private static final Logger LOGGER = LoggerFactory.getLogger(TraffExamServiceImpl.class);
74 
75  @Autowired
76  @Qualifier("traffExamRestTemplate")
77  private RestTemplate restTemplate;
78 
79  @Autowired
80  private TopologyDefinition topology;
81 
82  private Map<UUID, Host> hostsPool;
83  private Inet4NetworkPool addressPool;
84 
85  private Map<UUID, Address> suppliedAddresses = new HashMap<>();
86  private Map<UUID, HostResource> suppliedEndpoints = new HashMap<>();
87  private List<HostResource> failedToRelease = new LinkedList<>();
88 
89  private final RetryPolicy retryPolicy = new RetryPolicy()
90  .withDelay(1, TimeUnit.SECONDS)
91  .withMaxRetries(30);
92 
93  @PostConstruct
94  void initializePools() {
95  hostsPool = new HashMap<>();
96 
97  for (TraffGen traffGen : topology.getActiveTraffGens()) {
98  URI controlEndpoint;
99  try {
100  controlEndpoint = new URI(traffGen.getControlEndpoint());
101  } catch (URISyntaxException e) {
102  throw new IllegalArgumentException(String.format(
103  "Invalid traffGen(%s) REST endpoint address \"%s\": %s",
104  traffGen.getName(), traffGen.getControlEndpoint(), e.getMessage()), e);
105  }
106 
107  UUID id = UUID.randomUUID();
108  Host host = new Host(id, traffGen.getIfaceName(), controlEndpoint, traffGen.getName());
109 
110  try {
111  restTemplate.headForHeaders(makeHostUri(host).path("endpoint").build());
112  } catch (RestClientException ex) {
113  throw new IllegalArgumentException(String.format(
114  "The traffGen(%s) REST endpoint address \"%s\" can't be reached: %s",
115  traffGen.getName(), traffGen.getControlEndpoint(), ex.getMessage()), ex);
116  }
117 
118  hostsPool.put(id, host);
119  }
120  hostsPool = unmodifiableMap(hostsPool);
121 
122  TraffGenConfig config = topology.getTraffGenConfig();
123  Inet4Network network;
124  try {
125  network = new Inet4Network(
126  (Inet4Address) Inet4Address.getByName(config.getAddressPoolBase()),
127  config.getAddressPoolPrefixLen());
128  } catch (Inet4ValueException | UnknownHostException e) {
129  throw new InputMismatchException(String.format(
130  "Invalid traffGen address pool \"%s:%s\": %s",
131  config.getAddressPoolBase(), config.getAddressPoolPrefixLen(), e));
132  }
133  addressPool = new Inet4NetworkPool(network, 30);
134  }
135 
136  @Override
137  public List<Host> listHosts() {
138  return new ArrayList<>(hostsPool.values());
139  }
140 
141  @Override
142  public Host hostByName(String name) throws NoResultsFoundException {
143  if (name == null) {
144  throw new IllegalArgumentException("Argument \"name\" must not be null");
145  }
146 
147  Host target = null;
148  for (Host current : hostsPool.values()) {
149  if (!name.equals(current.getName())) {
150  continue;
151  }
152  target = current;
153  break;
154  }
155 
156  if (target == null) {
157  throw new NoResultsFoundException(String.format("There is no host with name \"%s\"", name));
158  }
159 
160  return target;
161  }
162 
163  @Override
165  checkHostPresence(exam.getSource());
166  checkHostPresence(exam.getDest());
167 
168  Inet4Network subnet;
169  try {
170  subnet = addressPool.allocate();
171  } catch (Inet4ValueException e) {
172  throw new OperationalException("Unable to allocate subnet for exam. There is no more addresses available.");
173  }
174 
175  ExamResources resources = null;
176  List<HostResource> supplied = new ArrayList<>(4);
177  try {
178  Address sourceAddress = new Address(subnet.address(1), subnet.getPrefix(), exam.getSourceVlan());
179  sourceAddress = assignAddress(exam.getSource(), sourceAddress);
180  supplied.add(sourceAddress);
181 
182  Address destAddress = new Address(subnet.address(2), subnet.getPrefix(), exam.getDestVlan());
183  destAddress = assignAddress(exam.getDest(), destAddress);
184  supplied.add(destAddress);
185 
186  ConsumerEndpoint consumer = assignEndpoint(exam.getDest(), new ConsumerEndpoint(destAddress.getId()));
187  supplied.add(consumer);
188 
190  sourceAddress.getId(),
191  new EndpointAddress(destAddress.getAddress(), consumer.getBindPort()));
192  if (exam.getBandwidthLimit() != null) {
193  producer.setBandwidth(exam.getBandwidthLimit());
194  producer.setBurstPkt(exam.getBurstPkt());
195  }
196  if (exam.getTimeLimitSeconds() != null) {
197  producer.setTime(exam.getTimeLimitSeconds());
198  }
199 
200  producer = assignEndpoint(exam.getSource(), producer);
201  supplied.add(producer);
202 
203  resources = new ExamResources(subnet, producer, consumer);
204  } catch (Inet4ValueException e) {
205  throw new OperationalException(
206  "Insufficient resources - not enough IP address in subnet. Check addressPool configuration.");
207  } finally {
208  if (resources == null) {
209  extendFailedToRelease(releaseResources(supplied));
210 
211  try {
212  addressPool.free(subnet);
213  } catch (Inet4ValueException e) {
214  // Unreachable point, free throw exception only if invalid (not allocated before) address passed
215  }
216  }
217  }
218 
219  return resources;
220  }
221 
222  @Override
223  public ExamReport waitExam(Exam exam) {
224  return this.waitExam(exam, true);
225  }
226 
227  @Override
228  public ExamReport waitExam(Exam exam, boolean cleanup) {
229  ExamReport result = Failsafe.with(retryPolicy
230  .retryIf((t, u) -> u instanceof ExamNotFinishedException))
231  .get(() -> fetchReport(exam));
232 
233  if (result != null && cleanup) {
234  stopExam(exam);
235  }
236 
237  return result;
238  }
239 
240  @Override
242  ExamResources resources = retrieveExamResources(exam);
243 
244  EndpointReport producerReport = fetchEndpointReport(resources.getProducer());
245  EndpointReport consumerReport;
246  try {
247  consumerReport = fetchEndpointReport(resources.getConsumer());
248  } catch (ExamNotFinishedException e) {
249  if (producerReport.getError() == null) {
250  throw e;
251  }
252  consumerReport = new EndpointReport("Don't wait for consumer report due to error on producer side");
253  }
254 
255  return new ExamReport(exam, producerReport, consumerReport);
256  }
257 
258  @Override
259  public void stopExam(Exam exam) throws NoResultsFoundException {
260  ExamResources resources = retrieveExamResources(exam);
261  List<HostResource> releaseQueue = new ArrayList<>(4);
262 
263  releaseQueue.add(resources.getProducer());
264  releaseQueue.add(resources.getConsumer());
265 
266  UUID addressId;
267 
268  Address address;
269  addressId = resources.getProducer().getBindAddressId();
270  if (addressId != null) {
271  address = suppliedAddresses.get(addressId);
272  checkHostRelation(address, suppliedAddresses);
273  releaseQueue.add(address);
274  }
275  addressId = resources.getConsumer().getBindAddressId();
276  if (addressId != null) {
277  address = suppliedAddresses.get(addressId);
278  checkHostRelation(address, suppliedAddresses);
279  releaseQueue.add(address);
280  }
281 
282  List<HostResource> failed = releaseResources(releaseQueue);
283  try {
284  // release time is not time critical so we can try to retry release call for "stuck" resources here
285  retryResourceRelease();
286  } finally {
287  extendFailedToRelease(failed);
288  }
289  }
290 
291  @Override
292  public void stopAll() {
293  List<HostResource> releaseQueue = new LinkedList<>();
294 
295  releaseQueue.addAll(suppliedEndpoints.values());
296  releaseQueue.addAll(suppliedAddresses.values());
297 
298  releaseQueue = releaseResources(releaseQueue);
299  try {
300  retryResourceRelease();
301  } finally {
302  extendFailedToRelease(releaseQueue);
303  }
304  }
305 
306  @Override
307  public void destroy() throws Exception {
308  stopAll();
309  }
310 
311  private Address assignAddress(Host host, Address payload) {
312  AddressResponse response = restTemplate.postForObject(
313  makeHostUri(host).path("address").build(), payload,
314  AddressResponse.class);
315 
316  Address address = response.address;
317  address.setHost(host);
318  suppliedAddresses.put(address.getId(), address);
319 
320  return address;
321  }
322 
323  private void releaseAddress(Address subject) {
324  restTemplate.delete(
325  makeHostUri(subject.getHost())
326  .path("address/")
327  .path(subject.getId().toString()).build());
328 
329  suppliedAddresses.remove(subject.getId());
330  subject.setHost(null);
331  }
332 
333  private <T extends Endpoint> T assignEndpoint(Host host, T payload) {
334  EndpointResponse response = restTemplate.postForObject(
335  makeHostUri(host).path("endpoint").build(),
336  payload, EndpointResponse.class);
337 
338  @SuppressWarnings("unchecked")
339  T endpoint = (T) response.endpoint;
340  endpoint.setHost(host);
341  suppliedEndpoints.put(endpoint.getId(), endpoint);
342 
343  return endpoint;
344  }
345 
346  private void releaseEndpoint(Endpoint endpoint) {
347  restTemplate.delete(
348  makeHostUri(endpoint.getHost())
349  .path("endpoint/")
350  .path(endpoint.getId().toString())
351  .build());
352 
353  suppliedEndpoints.remove(endpoint.getId());
354  }
355 
356  private EndpointReport fetchEndpointReport(Endpoint endpoint)
357  throws NoResultsFoundException, ExamNotFinishedException {
358  checkHostRelation(endpoint, suppliedEndpoints);
359 
360  ReportResponse report = restTemplate.getForObject(
361  makeHostUri(endpoint.getHost())
362  .path("endpoint/")
363  .path(endpoint.getId().toString())
364  .path("/report").build(),
365  ReportResponse.class);
366  if (report.getStatus() == null) {
367  throw new ExamNotFinishedException();
368  }
369 
370  return new EndpointReport(report);
371  }
372 
373  private synchronized void retryResourceRelease() {
374  failedToRelease = releaseResources(failedToRelease);
375  }
376 
377  private List<HostResource> releaseResources(List<HostResource> resources) {
378  List<HostResource> fail = new LinkedList<>();
379 
380  for (HostResource item : resources) {
381  try {
382  if (item instanceof Address) {
383  releaseAddress((Address) item);
384  } else if (item instanceof Endpoint) {
385  releaseEndpoint((Endpoint) item);
386  } else {
387  throw new RuntimeException("Unsupported resource");
388  }
389  } catch (HttpStatusCodeException e) {
390  if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
391  fail.add(item);
392  }
393  } catch (RestClientException e) {
394  fail.add(item);
395  }
396  }
397 
398  return fail;
399  }
400 
401  private synchronized void extendFailedToRelease(List<HostResource> resources) {
402  failedToRelease.addAll(resources);
403  }
404 
405  private ExamResources retrieveExamResources(Exam exam) throws NoResultsFoundException {
406  ExamResources resources = exam.getResources();
407  if (resources == null) {
408  throw new IllegalArgumentException("Exam resources are empty.");
409  }
410  checkExamRelation(resources);
411 
412  return resources;
413  }
414 
415  private void checkExamRelation(ExamResources resources) throws NoResultsFoundException {
416  checkHostRelation(resources.getProducer(), suppliedEndpoints);
417  checkHostRelation(resources.getConsumer(), suppliedEndpoints);
418  }
419 
420  private void checkHostRelation(
421  HostResource target, Map<UUID, ? extends HostResource> supplied)
422  throws NoResultsFoundException {
423  if (!supplied.containsKey(target.getId())) {
424  throw new NoResultsFoundException(
425  "Object is not supplied by this service.");
426  }
427  if (target.getHost() == null) {
428  throw new NoResultsFoundException(
429  "Object have no link to the host object.");
430  }
431  }
432 
433  private void checkHostPresence(Host subject)
434  throws NoResultsFoundException {
435  if (!hostsPool.containsKey(subject.getId())) {
436  throw new NoResultsFoundException(String.format(
437  "There is no host with id \"%s\"", subject.getId()));
438  }
439  }
440 
441  private UriBuilder makeHostUri(Host host) {
442  return UriComponentsBuilder.fromUri(host.getApiAddress());
443  }
444 }
name
Definition: setup.py:24
target
Definition: nodes.py:50
list result
Definition: plan-d.py:72
def build()
Definition: plan-e.py:73
net
Definition: plan-b.py:46