blob: 1a225ea9b9ad6a3035b751270175c7aa7b1df79d [file] [log] [blame]
Jian Li63430202018-08-30 16:24:09 +09001/*
2 * Copyright 2018-present Open Networking Foundation
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
17package org.onosproject.openstacknetworking.impl;
18
19import com.google.common.base.Strings;
20import org.apache.commons.lang.StringUtils;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Modified;
25import org.apache.felix.scr.annotations.Property;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.http.Header;
29import org.apache.http.HttpEntityEnclosingRequest;
30import org.apache.http.HttpMessage;
31import org.apache.http.HttpRequest;
32import org.apache.http.HttpResponse;
33import org.apache.http.client.methods.CloseableHttpResponse;
34import org.apache.http.client.methods.HttpGet;
35import org.apache.http.client.methods.HttpPost;
36import org.apache.http.client.methods.HttpRequestBase;
37import org.apache.http.impl.client.CloseableHttpClient;
38import org.apache.http.impl.client.HttpClientBuilder;
39import org.apache.http.message.BasicHeader;
40import org.apache.http.message.BasicHttpResponse;
41import org.onlab.packet.BasePacket;
42import org.onlab.packet.Data;
43import org.onlab.packet.Ethernet;
44import org.onlab.packet.IPv4;
45import org.onlab.packet.IpAddress;
46import org.onlab.packet.IpPrefix;
47import org.onlab.packet.TCP;
48import org.onlab.packet.TpPort;
49import org.onlab.util.Tools;
50import org.onosproject.cfg.ComponentConfigService;
51import org.onosproject.cluster.ClusterService;
52import org.onosproject.cluster.LeadershipService;
53import org.onosproject.cluster.NodeId;
54import org.onosproject.core.ApplicationId;
55import org.onosproject.core.CoreService;
56import org.onosproject.net.ConnectPoint;
57import org.onosproject.net.flow.DefaultTrafficSelector;
58import org.onosproject.net.flow.DefaultTrafficTreatment;
59import org.onosproject.net.flow.TrafficSelector;
60import org.onosproject.net.flow.TrafficTreatment;
61import org.onosproject.net.packet.DefaultOutboundPacket;
62import org.onosproject.net.packet.PacketContext;
63import org.onosproject.net.packet.PacketProcessor;
64import org.onosproject.net.packet.PacketService;
65import org.onosproject.openstacknetworking.api.Constants;
66import org.onosproject.openstacknetworking.api.InstancePort;
67import org.onosproject.openstacknetworking.api.InstancePortService;
68import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
69import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
70import org.onosproject.openstacknode.api.OpenstackNode;
71import org.onosproject.openstacknode.api.OpenstackNodeEvent;
72import org.onosproject.openstacknode.api.OpenstackNodeListener;
73import org.onosproject.openstacknode.api.OpenstackNodeService;
74import org.openstack4j.model.network.Port;
75import org.osgi.service.component.ComponentContext;
76import org.slf4j.Logger;
77
78import java.io.IOException;
79import java.nio.ByteBuffer;
80import java.util.Dictionary;
81import java.util.Objects;
82
83import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
84import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
85import static org.onosproject.openstacknetworking.impl.OpenstackMetadataProxyHandler.Http.Type.RESPONSE;
86import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.hmacEncrypt;
87import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.parseHttpRequest;
88import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.unparseHttpResponseBody;
89import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.unparseHttpResponseHeader;
90import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
91import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.CONTROLLER;
92import static org.slf4j.LoggerFactory.getLogger;
93
94/**
95 * Handles metadata requests for the virtual instances.
96 */
97@Component(immediate = true)
98public class OpenstackMetadataProxyHandler {
99 protected final Logger log = getLogger(getClass());
100
101 private static final String METADATA_SERVER_IP = "169.254.169.254";
102 private static final int METADATA_SERVER_PORT = 8775;
103 private static final int HTTP_SERVER_PORT = 80;
104 private static final int PREFIX_LENGTH = 32;
105 private static final short WINDOW_SIZE = (short) 0x1000;
106 private static final short FIN_FLAG = (short) 0x01;
107 private static final short SYN_FLAG = (short) 0x02;
108 private static final short ACK_FLAG = (short) 0x10;
109 private static final short SYN_ACK_FLAG = (short) 0x12;
110 private static final short FIN_ACK_FLAG = (short) 0x11;
111 private static final byte DATA_OFFSET = (byte) 0x5;
112 private static final short URGENT_POINTER = (short) 0x1;
113 private static final byte PACKET_TTL = (byte) 127;
114 private static final String HTTP_PREFIX = "http://";
115 private static final String COLON = ":";
116
117 private static final String INSTANCE_ID_HEADER = "X-Instance-ID";
118 private static final String INSTANCE_ID_SIGNATURE_HEADER = "X-Instance-ID-Signature";
119 private static final String TENANT_ID_HEADER = "X-Tenant-ID";
120 private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For";
121
122 private static final String HTTP_GET_METHOD = "GET";
123 private static final String HTTP_POST_METHOD = "POST";
124
125 private static final String METADATA_SECRET = "metadataSecret";
126 private static final String DEFAULT_METADATA_SECRET = "nova";
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected CoreService coreService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
132 protected ComponentConfigService configService;
133
134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
135 protected PacketService packetService;
136
137 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
138 protected ClusterService clusterService;
139
140 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
141 protected LeadershipService leadershipService;
142
143 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
144 protected OpenstackNetworkService osNetworkService;
145
146 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
147 protected OpenstackNodeService osNodeService;
148
149 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
150 protected InstancePortService instancePortService;
151
152 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
153 protected OpenstackFlowRuleService osFlowRuleService;
154
155 @Property(name = METADATA_SECRET, value = DEFAULT_METADATA_SECRET,
156 label = "Metadata secret")
157 private String metadataSecret = DEFAULT_METADATA_SECRET;
158
159 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
160 private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
161
162 private ApplicationId appId;
163 private NodeId localNodeId;
164
165 @Activate
166 protected void activate() {
167 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
168 localNodeId = clusterService.getLocalNode().id();
169 configService.registerProperties(getClass());
170 osNodeService.addListener(osNodeListener);
171 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
172 leadershipService.runForLeadership(appId.name());
173
174 log.info("Started");
175 }
176
177 @Deactivate
178 protected void deactivate() {
179 packetService.removeProcessor(packetProcessor);
180 configService.unregisterProperties(getClass(), false);
181 osNodeService.removeListener(osNodeListener);
182 leadershipService.withdraw(appId.name());
183
184 log.info("Stopped");
185 }
186
187 @Modified
188 protected void modified(ComponentContext context) {
189 Dictionary<?, ?> properties = context.getProperties();
190 String updatedMetadataSecret;
191
192 updatedMetadataSecret = Tools.get(properties, METADATA_SECRET);
193
194 if (!Strings.isNullOrEmpty(updatedMetadataSecret) &&
195 !updatedMetadataSecret.equals(metadataSecret)) {
196 metadataSecret = updatedMetadataSecret;
197 }
198
199 log.info("Modified");
200 }
201
202 private class InternalPacketProcessor implements PacketProcessor {
203
204 @Override
205 public void process(PacketContext context) {
206 if (context.isHandled()) {
207 return;
208 }
209
210 Ethernet ethPacket = context.inPacket().parsed();
211 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
212 return;
213 }
214
215 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
216 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_TCP ||
217 !IpAddress.valueOf(ipv4Packet.getDestinationAddress()).
218 equals(IpAddress.valueOf(METADATA_SERVER_IP))) {
219 return;
220 }
221
222 TCP tcpPacket = (TCP) ipv4Packet.getPayload();
223 if (tcpPacket.getDestinationPort() != HTTP_SERVER_PORT) {
224 return;
225 }
226
227 if (tcpPacket.getFlags() == SYN_FLAG) {
228 Ethernet ethReply = buildTcpSynAckPacket(ethPacket, ipv4Packet, tcpPacket);
229 sendReply(context, ethReply);
230 return;
231 }
232
233 if (tcpPacket.getFlags() == FIN_ACK_FLAG) {
234 Ethernet ackReply = buildTcpAckPacket(ethPacket, ipv4Packet, tcpPacket);
235 sendReply(context, ackReply);
236 Ethernet finAckReply = buildTcpFinAckPacket(ethPacket, ipv4Packet, tcpPacket);
237 sendReply(context, finAckReply);
238 return;
239 }
240
241 Data data = (Data) tcpPacket.getPayload();
242 byte[] byteData = data.getData();
243
244 if (byteData.length != 0) {
245 HttpRequest request = parseHttpRequest(byteData);
246 ConnectPoint cp = context.inPacket().receivedFrom();
247 InstancePort instPort = instancePortService.instancePort(cp.deviceId(), cp.port());
248
249 if (instPort == null || request == null) {
250 log.warn("Cannot send metadata request due to lack of information");
251 return;
252 }
253
254 // attempt to send HTTP request to the meta-data server (nova-api),
255 // obtain the HTTP response
256 CloseableHttpResponse proxyResponse = proxyHttpRequest(request, instPort);
257
258 if (proxyResponse == null) {
259 log.warn("No response was received from metadata server");
260 return;
261 }
262
263 HttpResponse response = new BasicHttpResponse(proxyResponse.getStatusLine());
264 response.setEntity(proxyResponse.getEntity());
265 response.setHeaders(proxyResponse.getAllHeaders());
266
267 Http httpResponse = new Http();
268 httpResponse.setType(RESPONSE);
269 httpResponse.setMessage(response);
270
271 TCP tcpReply = buildTcpDataPacket(tcpPacket, byteData.length, response);
272 Ethernet ethReply = buildEthFrame(ethPacket, ipv4Packet, tcpReply);
273 sendReply(context, ethReply);
274
275 try {
276 proxyResponse.close();
277 } catch (IOException e) {
278 log.warn("Failed to close the response connection due to {}", e);
279 }
280 }
281 }
282
283 /**
284 * Builds an ethernet frame contains TCP sync-ack packet generated
285 * from the given TCP sync request packet.
286 *
287 * @param ethRequest ethernet request frame
288 * @param ipv4Request IPv4 request
289 * @param tcpRequest TCP request
290 * @return an ethernet frame contains newly generated TCP reply
291 */
292 private Ethernet buildTcpSynAckPacket(Ethernet ethRequest,
293 IPv4 ipv4Request, TCP tcpRequest) {
294
295 TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getSequence(),
296 tcpRequest.getSequence() + 1, SYN_ACK_FLAG);
297
298 return buildEthFrame(ethRequest, ipv4Request, tcpReply);
299 }
300
301 /**
302 * Builds a TCP ACK packet receiving SYN packet.
303 *
304 * @param ethRequest ethernet request frame
305 * @param ipv4Request IPv4 request
306 * @param tcpRequest TCP request
307 * @return an ethernet frame contains newly generated TCP reply
308 */
309 private Ethernet buildTcpAckPacket(Ethernet ethRequest,
310 IPv4 ipv4Request, TCP tcpRequest) {
311 TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getAcknowledge(),
312 tcpRequest.getSequence() + 1, ACK_FLAG);
313
314 return buildEthFrame(ethRequest, ipv4Request, tcpReply);
315 }
316
317 /**
318 * Builds a TCP FIN-ACK packet receiving FIN-ACK packet.
319 *
320 * @param ethRequest ethernet request frame
321 * @param ipv4Request IPv4 request
322 * @param tcpRequest TCP request
323 * @return an ethernet frame contains newly generated TCP reply
324 */
325 private Ethernet buildTcpFinAckPacket(Ethernet ethRequest,
326 IPv4 ipv4Request, TCP tcpRequest) {
327 TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getAcknowledge(),
328 tcpRequest.getSequence() + 1, FIN_ACK_FLAG);
329
330 return buildEthFrame(ethRequest, ipv4Request, tcpReply);
331 }
332
333 /**
334 * Builds a TCP signaling packet.
335 *
336 * @param tcpRequest TCP request
337 * @param seq sequence number
338 * @param ack ack number
339 * @param flags TCP flags
340 * @return TCP signal packet
341 */
342 private TCP buildTcpSignalPacket(TCP tcpRequest, int seq, int ack, short flags) {
343 TCP tcpReply = new TCP();
344 tcpReply.setSourcePort(tcpRequest.getDestinationPort());
345 tcpReply.setDestinationPort(tcpRequest.getSourcePort());
346 tcpReply.setSequence(seq);
347 tcpReply.setAcknowledge(ack);
348 tcpReply.setDataOffset(DATA_OFFSET);
349 tcpReply.setFlags(flags);
350 tcpReply.setWindowSize(WINDOW_SIZE);
351 tcpReply.setUrgentPointer(URGENT_POINTER);
352
353 return tcpReply;
354 }
355
356 /**
357 * Builds a TCP data packet.
358 *
359 * @param tcpRequest TCP request
360 * @param requestLength TCP request data length
361 * @param response HTTP response
362 * @return a TCP data packet
363 */
364 private TCP buildTcpDataPacket(TCP tcpRequest, int requestLength,
365 HttpResponse response) {
366 TCP tcpReply = new TCP();
367 tcpReply.setSourcePort(tcpRequest.getDestinationPort());
368 tcpReply.setDestinationPort(tcpRequest.getSourcePort());
369 tcpReply.setSequence(tcpRequest.getAcknowledge());
370 tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
371 tcpReply.setDataOffset(DATA_OFFSET); // no options
372 tcpReply.setFlags(ACK_FLAG);
373 tcpReply.setWindowSize(WINDOW_SIZE);
374 tcpReply.setUrgentPointer(URGENT_POINTER);
375
376 Http httpResponse = new Http();
377 httpResponse.setType(RESPONSE);
378 httpResponse.setMessage(response);
379
380 tcpReply.setPayload(httpResponse);
381
382 return tcpReply;
383 }
384
385 /**
386 * Builds an ethernet frame with the given IPv4 and TCP payload.
387 *
388 * @param ethRequest ethernet request frame
389 * @param ipv4Request IPv4 request
390 * @param tcpReply TCP reply
391 * @return an ethernet frame contains TCP payload
392 */
393 private Ethernet buildEthFrame(Ethernet ethRequest, IPv4 ipv4Request,
394 TCP tcpReply) {
395 Ethernet ethReply = new Ethernet();
396 ethReply.setSourceMACAddress(ethRequest.getDestinationMAC());
397 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
398 ethReply.setEtherType(ethRequest.getEtherType());
399
400 IPv4 ipv4Reply = new IPv4();
401 ipv4Reply.setSourceAddress(ipv4Request.getDestinationAddress());
402 ipv4Reply.setDestinationAddress(ipv4Request.getSourceAddress());
403 ipv4Reply.setTtl(PACKET_TTL);
404
405 ipv4Reply.setPayload(tcpReply);
406 ethReply.setPayload(ipv4Reply);
407
408 return ethReply;
409 }
410
411 /**
412 * Proxyies HTTP request.
413 *
414 * @param oldRequest HTTP request
415 * @param instPort instance port
416 * @return HTTP response
417 */
418 private CloseableHttpResponse proxyHttpRequest(HttpRequest oldRequest,
419 InstancePort instPort) {
420
421 CloseableHttpClient client = HttpClientBuilder.create().build();
422 OpenstackNode controller = osNodeService.completeNodes(CONTROLLER).
423 stream().findFirst().orElse(null);
424 if (controller == null) {
425 return null;
426 }
427
428 String path = oldRequest.getRequestLine().getUri();
429 String url = HTTP_PREFIX + controller.managementIp().toString() +
430 COLON + METADATA_SERVER_PORT + path;
431
432 if (StringUtils.isEmpty(url)) {
433 log.warn("The metadata endpoint is not configured!");
434 return null;
435 }
436
437 log.info("Sending request to metadata endpoint {}...", url);
438
439 HttpRequestBase request;
440
441 switch (oldRequest.getRequestLine().getMethod()) {
442 case HTTP_GET_METHOD:
443 request = new HttpGet(url);
444 break;
445 case HTTP_POST_METHOD:
446 request = new HttpPost(url);
447 HttpEntityEnclosingRequest entityRequest =
448 (HttpEntityEnclosingRequest) oldRequest;
449 ((HttpPost) request).setEntity(entityRequest.getEntity());
450 break;
451 default:
452 request = new HttpGet(url);
453 break;
454 }
455
456 // configure headers from original HTTP request
457 for (Header header : oldRequest.getAllHeaders()) {
458 request.addHeader(header);
459 }
460
461 request.setProtocolVersion(oldRequest.getProtocolVersion());
462
463 Port port = osNetworkService.port(instPort.portId());
464
465 request.addHeader(new BasicHeader(INSTANCE_ID_HEADER, port.getDeviceId()));
466 request.addHeader(new BasicHeader(INSTANCE_ID_SIGNATURE_HEADER,
467 hmacEncrypt(metadataSecret, port.getDeviceId())));
468 request.addHeader(new BasicHeader(TENANT_ID_HEADER, port.getTenantId()));
469 request.addHeader(new BasicHeader(
470 FORWARDED_FOR_HEADER, instPort.ipAddress().toString()));
471
472 try {
473 return client.execute(request);
474 } catch (IOException e) {
475 log.warn("Failed to get response from metadata server due to {}", e);
476 }
477
478 return null;
479 }
480
481 /**
482 * Sends out ethernet frame.
483 *
484 * @param context packet context
485 * @param ethReply ethernet frame
486 */
487 private void sendReply(PacketContext context, Ethernet ethReply) {
488 if (ethReply == null) {
489 return;
490 }
491 ConnectPoint srcPoint = context.inPacket().receivedFrom();
492 TrafficTreatment treatment = DefaultTrafficTreatment
493 .builder()
494 .setOutput(srcPoint.port())
495 .build();
496
497 packetService.emit(new DefaultOutboundPacket(
498 srcPoint.deviceId(),
499 treatment,
500 ByteBuffer.wrap(ethReply.serialize())));
501 context.block();
502 }
503 }
504
505 private class InternalNodeEventListener implements OpenstackNodeListener {
506 @Override
507 public boolean isRelevant(OpenstackNodeEvent event) {
508 // do not allow to proceed without leadership
509 NodeId leader = leadershipService.getLeader(appId.name());
510 return Objects.equals(localNodeId, leader) &&
511 event.subject().type() == COMPUTE;
512 }
513
514 @Override
515 public void event(OpenstackNodeEvent event) {
516 OpenstackNode osNode = event.subject();
517 switch (event.type()) {
518 case OPENSTACK_NODE_COMPLETE:
519 setMetadataRule(osNode, true);
520 break;
521 case OPENSTACK_NODE_INCOMPLETE:
522 setMetadataRule(osNode, false);
523 break;
524 case OPENSTACK_NODE_CREATED:
525 case OPENSTACK_NODE_UPDATED:
526 case OPENSTACK_NODE_REMOVED:
527 default:
528 break;
529 }
530 }
531
532 /**
533 * Installs metadata rule for receiving all metadata request packets.
534 *
535 * @param osNode openstack node
536 * @param install installation flag
537 */
538 private void setMetadataRule(OpenstackNode osNode, boolean install) {
539 TrafficSelector selector = DefaultTrafficSelector.builder()
540 .matchEthType(Ethernet.TYPE_IPV4)
541 .matchIPProtocol(IPv4.PROTOCOL_TCP)
542 .matchIPDst(IpPrefix.valueOf(
543 IpAddress.valueOf(METADATA_SERVER_IP), PREFIX_LENGTH))
544 .matchTcpDst(TpPort.tpPort(HTTP_SERVER_PORT))
545 .build();
546
547 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
548 .punt()
549 .build();
550
551 osFlowRuleService.setRule(
552 appId,
553 osNode.intgBridge(),
554 selector,
555 treatment,
556 PRIORITY_DHCP_RULE,
557 DHCP_ARP_TABLE,
558 install);
559 }
560 }
561
562 /**
563 * Implements Http packet format.
564 */
565 protected static class Http extends BasePacket {
566
567 public enum Type {
568
569 /**
570 * Signifies that this is a Http REQUEST packet.
571 */
572 REQUEST,
573
574 /**
575 * Signifies that this is a Http RESPONSE packet.
576 */
577 RESPONSE,
578 }
579
580 private Type type;
581 private HttpMessage message;
582
583 /**
584 * Obtains the Http type.
585 *
586 * @return Http type
587 */
588 public Type getType() {
589 return type;
590 }
591
592 /**
593 * Configures the Http type.
594 *
595 * @param type Http type
596 */
597 public void setType(Type type) {
598 this.type = type;
599 }
600
601 /**
602 * Obtains the Http message.
603 *
604 * @return Http message
605 */
606 public HttpMessage getMessage() {
607 return message;
608 }
609
610 /**
611 * Configures the Http message.
612 *
613 * @param message Http message
614 */
615 public void setMessage(HttpMessage message) {
616 this.message = message;
617 }
618
619 @Override
620 public byte[] serialize() {
621 if (type == RESPONSE) {
622
623 byte[] header = unparseHttpResponseHeader((HttpResponse) message);
624 byte[] body = unparseHttpResponseBody((HttpResponse) message);
625
626 if (header == null || body == null) {
627 return new byte[0];
628 }
629
630 final byte[] data = new byte[header.length + body.length];
631 final ByteBuffer bb = ByteBuffer.wrap(data);
632 bb.put(header);
633 bb.put(body);
634
635 return data;
636 }
637 return new byte[0];
638 }
639
640 @Override
641 public boolean equals(Object o) {
642
643 if (this == o) {
644 return true;
645 }
646
647 if (o == null || getClass() != o.getClass()) {
648 return false;
649 }
650
651 if (!super.equals(o)) {
652 return false;
653 }
654
655 Http http = (Http) o;
656 return type == http.type &&
657 com.google.common.base.Objects.equal(message, http.message);
658 }
659
660 @Override
661 public int hashCode() {
662 return Objects.hash(type, message);
663 }
664 }
665}