blob: 8d16bcee4fd9998709a6500c8a9654c7c5b691e9 [file] [log] [blame]
Hyunsun Moon44aac662017-02-18 02:07:01 +09001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Hyunsun Moon44aac662017-02-18 02:07:01 +09003 *
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.openstacknetworking.impl;
17
18import com.google.common.base.Strings;
19import com.google.common.collect.ImmutableSet;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
daniel parkb5817102018-02-15 00:18:51 +090026import org.onlab.packet.ARP;
27import org.onlab.packet.Ethernet;
28import org.onlab.packet.IpAddress;
29import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
31import org.onlab.util.KryoNamespace;
32import org.onosproject.core.ApplicationId;
Hyunsun Moon44aac662017-02-18 02:07:01 +090033import org.onosproject.core.CoreService;
34import org.onosproject.event.ListenerRegistry;
daniel parkb5817102018-02-15 00:18:51 +090035import org.onosproject.net.device.DeviceService;
36import org.onosproject.net.flow.DefaultTrafficTreatment;
37import org.onosproject.net.flow.TrafficTreatment;
38import org.onosproject.net.packet.DefaultOutboundPacket;
39import org.onosproject.net.packet.PacketService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090040import org.onosproject.openstacknetworking.api.Constants;
daniel parkb5817102018-02-15 00:18:51 +090041import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
Hyunsun Moon44aac662017-02-18 02:07:01 +090042import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
43import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
44import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
45import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
46import org.onosproject.openstacknetworking.api.OpenstackNetworkStore;
47import org.onosproject.openstacknetworking.api.OpenstackNetworkStoreDelegate;
daniel parkb5817102018-02-15 00:18:51 +090048import org.onosproject.openstacknode.api.OpenstackNode;
49import org.onosproject.openstacknode.api.OpenstackNodeService;
50import org.onosproject.store.serializers.KryoNamespaces;
51import org.onosproject.store.service.ConsistentMap;
52import org.onosproject.store.service.Serializer;
53import org.onosproject.store.service.StorageService;
54import org.onosproject.store.service.Versioned;
55import org.openstack4j.model.network.ExternalGateway;
56import org.openstack4j.model.network.IP;
Hyunsun Moon44aac662017-02-18 02:07:01 +090057import org.openstack4j.model.network.Network;
58import org.openstack4j.model.network.Port;
daniel parkb5817102018-02-15 00:18:51 +090059import org.openstack4j.model.network.Router;
Hyunsun Moon44aac662017-02-18 02:07:01 +090060import org.openstack4j.model.network.Subnet;
61import org.slf4j.Logger;
62
daniel parkb5817102018-02-15 00:18:51 +090063import java.nio.ByteBuffer;
64import java.util.NoSuchElementException;
Hyunsun Moon44aac662017-02-18 02:07:01 +090065import java.util.Objects;
66import java.util.Optional;
67import java.util.Set;
68import java.util.stream.Collectors;
69
70import static com.google.common.base.Preconditions.checkArgument;
71import static com.google.common.base.Preconditions.checkNotNull;
72import static org.onosproject.net.AnnotationKeys.PORT_NAME;
73import static org.slf4j.LoggerFactory.getLogger;
74
75/**
76 * Provides implementation of administering and interfacing OpenStack network,
77 * subnet, and port.
78 */
daniel parkb5817102018-02-15 00:18:51 +090079
Hyunsun Moon44aac662017-02-18 02:07:01 +090080@Service
81@Component(immediate = true)
82public class OpenstackNetworkManager
83 extends ListenerRegistry<OpenstackNetworkEvent, OpenstackNetworkListener>
84 implements OpenstackNetworkAdminService, OpenstackNetworkService {
85
86 protected final Logger log = getLogger(getClass());
87
88 private static final String MSG_NETWORK = "OpenStack network %s %s";
89 private static final String MSG_SUBNET = "OpenStack subnet %s %s";
90 private static final String MSG_PORT = "OpenStack port %s %s";
91 private static final String MSG_CREATED = "created";
92 private static final String MSG_UPDATED = "updated";
93 private static final String MSG_REMOVED = "removed";
94
95 private static final String ERR_NULL_NETWORK = "OpenStack network cannot be null";
96 private static final String ERR_NULL_NETWORK_ID = "OpenStack network ID cannot be null";
97 private static final String ERR_NULL_NETWORK_NAME = "OpenStack network name cannot be null";
98 private static final String ERR_NULL_SUBNET = "OpenStack subnet cannot be null";
99 private static final String ERR_NULL_SUBNET_ID = "OpenStack subnet ID cannot be null";
100 private static final String ERR_NULL_SUBNET_NET_ID = "OpenStack subnet network ID cannot be null";
101 private static final String ERR_NULL_SUBNET_CIDR = "OpenStack subnet CIDR cannot be null";
102 private static final String ERR_NULL_PORT = "OpenStack port cannot be null";
103 private static final String ERR_NULL_PORT_ID = "OpenStack port ID cannot be null";
104 private static final String ERR_NULL_PORT_NET_ID = "OpenStack port network ID cannot be null";
105
daniel parkb5817102018-02-15 00:18:51 +0900106 private static final String ERR_NOT_FOUND = " does not exist";
Hyunsun Moon44aac662017-02-18 02:07:01 +0900107 private static final String ERR_IN_USE = " still in use";
daniel parkb5817102018-02-15 00:18:51 +0900108 private static final String ERR_DUPLICATE = " already exists";
Hyunsun Moon44aac662017-02-18 02:07:01 +0900109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected CoreService coreService;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
daniel parkb5817102018-02-15 00:18:51 +0900114 protected PacketService packetService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected DeviceService deviceService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900120 protected OpenstackNetworkStore osNetworkStore;
121
daniel parkb5817102018-02-15 00:18:51 +0900122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected StorageService storageService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected OpenstackNodeService osNodeService;
127
128
Hyunsun Moon44aac662017-02-18 02:07:01 +0900129 private final OpenstackNetworkStoreDelegate delegate = new InternalNetworkStoreDelegate();
130
daniel parkb5817102018-02-15 00:18:51 +0900131 private ConsistentMap<String, ExternalPeerRouter> externalPeerRouterMap;
132
133 private static final KryoNamespace SERIALIZER_EXTERNAL_PEER_ROUTER_MAP = KryoNamespace.newBuilder()
134 .register(KryoNamespaces.API)
135 .register(ExternalPeerRouter.class)
136 .register(DefaultExternalPeerRouter.class)
137 .register(MacAddress.class)
138 .register(IpAddress.class)
139 .register(VlanId.class)
140 .build();
141
142 private ApplicationId appId;
143
144
Hyunsun Moon44aac662017-02-18 02:07:01 +0900145 @Activate
146 protected void activate() {
daniel parkb5817102018-02-15 00:18:51 +0900147 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
148
Hyunsun Moon44aac662017-02-18 02:07:01 +0900149 osNetworkStore.setDelegate(delegate);
150 log.info("Started");
daniel parkb5817102018-02-15 00:18:51 +0900151
152 externalPeerRouterMap = storageService.<String, ExternalPeerRouter>consistentMapBuilder()
153 .withSerializer(Serializer.using(SERIALIZER_EXTERNAL_PEER_ROUTER_MAP))
154 .withName("external-routermap")
155 .withApplicationId(appId)
156 .build();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900157 }
158
159 @Deactivate
160 protected void deactivate() {
161 osNetworkStore.unsetDelegate(delegate);
162 log.info("Stopped");
163 }
164
165 @Override
166 public void createNetwork(Network osNet) {
167 checkNotNull(osNet, ERR_NULL_NETWORK);
168 checkArgument(!Strings.isNullOrEmpty(osNet.getId()), ERR_NULL_NETWORK_ID);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900169
170 osNetworkStore.createNetwork(osNet);
171 log.info(String.format(MSG_NETWORK, osNet.getName(), MSG_CREATED));
172 }
173
174 @Override
175 public void updateNetwork(Network osNet) {
176 checkNotNull(osNet, ERR_NULL_NETWORK);
177 checkArgument(!Strings.isNullOrEmpty(osNet.getId()), ERR_NULL_NETWORK_ID);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900178
179 osNetworkStore.updateNetwork(osNet);
180 log.info(String.format(MSG_NETWORK, osNet.getId(), MSG_UPDATED));
181 }
182
183 @Override
184 public void removeNetwork(String netId) {
185 checkArgument(!Strings.isNullOrEmpty(netId), ERR_NULL_NETWORK_ID);
186 synchronized (this) {
187 if (isNetworkInUse(netId)) {
188 final String error = String.format(MSG_NETWORK, netId, ERR_IN_USE);
189 throw new IllegalStateException(error);
190 }
191 Network osNet = osNetworkStore.removeNetwork(netId);
192 if (osNet != null) {
193 log.info(String.format(MSG_NETWORK, osNet.getName(), MSG_REMOVED));
194 }
195 }
196 }
197
198 @Override
199 public void createSubnet(Subnet osSubnet) {
200 checkNotNull(osSubnet, ERR_NULL_SUBNET);
201 checkArgument(!Strings.isNullOrEmpty(osSubnet.getId()), ERR_NULL_SUBNET_ID);
202 checkArgument(!Strings.isNullOrEmpty(osSubnet.getNetworkId()), ERR_NULL_SUBNET_NET_ID);
203 checkArgument(!Strings.isNullOrEmpty(osSubnet.getCidr()), ERR_NULL_SUBNET_CIDR);
204
205 osNetworkStore.createSubnet(osSubnet);
206 log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_CREATED));
207 }
208
209 @Override
210 public void updateSubnet(Subnet osSubnet) {
211 checkNotNull(osSubnet, ERR_NULL_SUBNET);
212 checkArgument(!Strings.isNullOrEmpty(osSubnet.getId()), ERR_NULL_SUBNET_ID);
213 checkArgument(!Strings.isNullOrEmpty(osSubnet.getNetworkId()), ERR_NULL_SUBNET_NET_ID);
214 checkArgument(!Strings.isNullOrEmpty(osSubnet.getCidr()), ERR_NULL_SUBNET_CIDR);
215
216 osNetworkStore.updateSubnet(osSubnet);
217 log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_UPDATED));
218 }
219
220 @Override
221 public void removeSubnet(String subnetId) {
222 checkArgument(!Strings.isNullOrEmpty(subnetId), ERR_NULL_SUBNET_ID);
223 synchronized (this) {
224 if (isSubnetInUse(subnetId)) {
225 final String error = String.format(MSG_SUBNET, subnetId, ERR_IN_USE);
226 throw new IllegalStateException(error);
227 }
228 Subnet osSubnet = osNetworkStore.removeSubnet(subnetId);
229 if (osSubnet != null) {
230 log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_REMOVED));
231 }
232 }
233 }
234
235 @Override
236 public void createPort(Port osPort) {
237 checkNotNull(osPort, ERR_NULL_PORT);
238 checkArgument(!Strings.isNullOrEmpty(osPort.getId()), ERR_NULL_PORT_ID);
239 checkArgument(!Strings.isNullOrEmpty(osPort.getNetworkId()), ERR_NULL_PORT_NET_ID);
240
241 osNetworkStore.createPort(osPort);
242 log.info(String.format(MSG_PORT, osPort.getId(), MSG_CREATED));
243 }
244
245 @Override
246 public void updatePort(Port osPort) {
247 checkNotNull(osPort, ERR_NULL_PORT);
248 checkArgument(!Strings.isNullOrEmpty(osPort.getId()), ERR_NULL_PORT_ID);
249 checkArgument(!Strings.isNullOrEmpty(osPort.getNetworkId()), ERR_NULL_PORT_NET_ID);
250
251 osNetworkStore.updatePort(osPort);
Hyunsun Moonb7a9cd22017-02-24 11:12:53 +0900252 log.info(String.format(MSG_PORT, osPort.getId(), MSG_UPDATED));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900253 }
254
255 @Override
256 public void removePort(String portId) {
257 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_PORT_ID);
258 synchronized (this) {
259 if (isPortInUse(portId)) {
260 final String error = String.format(MSG_PORT, portId, ERR_IN_USE);
261 throw new IllegalStateException(error);
262 }
263 Port osPort = osNetworkStore.removePort(portId);
264 if (osPort != null) {
Hyunsun Moonb7a9cd22017-02-24 11:12:53 +0900265 log.info(String.format(MSG_PORT, osPort.getId(), MSG_REMOVED));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900266 }
267 }
268 }
269
270 @Override
Hyunsun Moonc7219222017-03-27 11:05:59 +0900271 public void clear() {
272 osNetworkStore.clear();
273 }
274
275 @Override
Hyunsun Moon44aac662017-02-18 02:07:01 +0900276 public Network network(String netId) {
277 checkArgument(!Strings.isNullOrEmpty(netId), ERR_NULL_NETWORK_ID);
278 return osNetworkStore.network(netId);
279 }
280
281 @Override
282 public Set<Network> networks() {
283 return osNetworkStore.networks();
284 }
285
286 @Override
287 public Subnet subnet(String subnetId) {
288 checkArgument(!Strings.isNullOrEmpty(subnetId), ERR_NULL_SUBNET_ID);
289 return osNetworkStore.subnet(subnetId);
290 }
291
292 @Override
293 public Set<Subnet> subnets() {
294 return osNetworkStore.subnets();
295 }
296
297 @Override
298 public Set<Subnet> subnets(String netId) {
299 Set<Subnet> osSubnets = osNetworkStore.subnets().stream()
300 .filter(subnet -> Objects.equals(subnet.getNetworkId(), netId))
301 .collect(Collectors.toSet());
302 return ImmutableSet.copyOf(osSubnets);
303 }
304
305 @Override
306 public Port port(String portId) {
307 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_PORT_ID);
308 return osNetworkStore.port(portId);
309 }
310
311 @Override
312 public Port port(org.onosproject.net.Port port) {
313 String portName = port.annotations().value(PORT_NAME);
314 if (Strings.isNullOrEmpty(portName)) {
315 return null;
316 }
317 Optional<Port> osPort = osNetworkStore.ports()
318 .stream()
319 .filter(p -> p.getId().contains(portName.substring(3)))
320 .findFirst();
daniel parkb5817102018-02-15 00:18:51 +0900321 return osPort.orElse(null);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900322 }
323
324 @Override
325 public Set<Port> ports() {
daniel parkb5817102018-02-15 00:18:51 +0900326 return ImmutableSet.copyOf(osNetworkStore.ports());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900327 }
328
329 @Override
330 public Set<Port> ports(String netId) {
331 Set<Port> osPorts = osNetworkStore.ports().stream()
332 .filter(port -> Objects.equals(port.getNetworkId(), netId))
333 .collect(Collectors.toSet());
334 return ImmutableSet.copyOf(osPorts);
335 }
336
daniel parkb5817102018-02-15 00:18:51 +0900337 @Override
338 public ExternalPeerRouter externalPeerRouter(IpAddress ipAddress) {
339 if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
340 return externalPeerRouterMap.get(ipAddress.toString()).value();
341 }
342 return null;
343 }
344
345 @Override
346 public void deriveExternalPeerRouterMac(ExternalGateway externalGateway, Router router) {
347 log.info("deriveExternalPeerRouterMac called");
348
349 IpAddress sourceIp = getExternalGatewaySourceIp(externalGateway, router);
350 IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
351
352 if (sourceIp == null || targetIp == null) {
353 log.warn("Failed to derive external router mac address because source IP {} or target IP {} is null",
354 sourceIp, targetIp);
355 return;
356 }
357
358 if (externalPeerRouterMap.containsKey(targetIp.toString()) &&
359 !externalPeerRouterMap.get(
360 targetIp.toString()).value().externalPeerRouterMac().equals(MacAddress.NONE)) {
361 return;
362 }
363
364 MacAddress sourceMac = Constants.DEFAULT_GATEWAY_MAC;
365 Ethernet ethRequest = ARP.buildArpRequest(sourceMac.toBytes(),
366 sourceIp.toOctets(),
367 targetIp.toOctets(),
368 VlanId.NO_VID);
369
370 if (osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY).isEmpty()) {
371 log.warn("There's no complete gateway");
372 return;
373 }
374 OpenstackNode gatewayNode = osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
375 .stream()
376 .findFirst()
377 .orElse(null);
378
379 if (gatewayNode == null) {
380 return;
381 }
382
383 String upLinkPort = gatewayNode.uplinkPort();
384
385 org.onosproject.net.Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
386 .filter(p -> Objects.equals(p.annotations().value(PORT_NAME), upLinkPort))
387 .findAny().orElse(null);
388
389 if (port == null) {
390 log.warn("There's no uplink port for gateway node {}", gatewayNode.toString());
391 return;
392 }
393
394 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
395 .setOutput(port.number())
396 .build();
397
398 packetService.emit(new DefaultOutboundPacket(
399 gatewayNode.intgBridge(),
400 treatment,
401 ByteBuffer.wrap(ethRequest.serialize())));
402
403 externalPeerRouterMap.put(
404 targetIp.toString(), new DefaultExternalPeerRouter(targetIp, MacAddress.NONE, VlanId.NONE));
405
406 log.info("Initializes external peer router map with peer router IP {}", targetIp.toString());
407 }
408
409 @Override
410 public void deleteExternalPeerRouter(ExternalGateway externalGateway) {
411 IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
412 if (targetIp == null) {
413 return;
414 }
415
416 if (externalPeerRouterMap.containsKey(targetIp.toString())) {
417 externalPeerRouterMap.remove(targetIp.toString());
418 }
419 }
420
421 private IpAddress getExternalGatewaySourceIp(ExternalGateway externalGateway, Router router) {
422 Port exGatewayPort = ports(externalGateway.getNetworkId())
423 .stream()
424 .filter(port -> Objects.equals(port.getDeviceId(), router.getId()))
425 .findAny().orElse(null);
426 if (exGatewayPort == null) {
427 log.warn("no external gateway port for router({})", router.getName());
428 return null;
429 }
430
431 IP ipAddress = exGatewayPort.getFixedIps().stream().findFirst().orElse(null);
432
433 return ipAddress == null ? null : IpAddress.valueOf(ipAddress.getIpAddress());
434 }
435
436 private IpAddress getExternalPeerRouterIp(ExternalGateway externalGateway) {
437 Optional<Subnet> externalSubnet = subnets(externalGateway.getNetworkId())
438 .stream()
439 .findFirst();
440
441 if (externalSubnet.isPresent()) {
442 return IpAddress.valueOf(externalSubnet.get().getGateway());
443 } else {
444 return null;
445 }
446 }
447
448 @Override
449 public void updateExternalPeerRouterMac(IpAddress ipAddress, MacAddress macAddress) {
450 try {
451 externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
452 new DefaultExternalPeerRouter(ipAddress, macAddress, existing.externalPeerRouterVlanId()));
453 } catch (Exception e) {
454 log.error("Exception occurred because of {}", e.toString());
455 }
456
457 log.info("Updated external peer router map {}",
458 externalPeerRouterMap.get(ipAddress.toString()).value().toString());
459 }
460
461
462 @Override
463 public void updateExternalPeerRouter(IpAddress ipAddress, MacAddress macAddress, VlanId vlanId) {
464 try {
465 externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
466 new DefaultExternalPeerRouter(ipAddress, macAddress, vlanId));
467 } catch (Exception e) {
468 log.error("Exception occurred because of {}", e.toString());
469 }
470 }
471
472 @Override
473 public MacAddress externalPeerRouterMac(ExternalGateway externalGateway) {
474 IpAddress ipAddress = getExternalPeerRouterIp(externalGateway);
475
476 if (ipAddress == null) {
477 return null;
478 }
479 if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
480 return externalPeerRouterMap.get(ipAddress.toString()).value().externalPeerRouterMac();
481 } else {
482 throw new NoSuchElementException();
483 }
484 }
485
486 @Override
487 public void updateExternalPeerRouterVlan(IpAddress ipAddress, VlanId vlanId) {
488
489 try {
490 externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) -> {
491 return new DefaultExternalPeerRouter(ipAddress, existing.externalPeerRouterMac(), vlanId);
492 });
493 } catch (Exception e) {
494 log.error("Exception occurred because of {}", e.toString());
495 }
496 }
497
498 @Override
499 public Set<ExternalPeerRouter> externalPeerRouters() {
500 Set<ExternalPeerRouter> externalPeerRouters = externalPeerRouterMap.values().stream()
501 .map(Versioned::value)
502 .collect(Collectors.toSet());
503 return ImmutableSet.copyOf(externalPeerRouters);
504 }
505
Hyunsun Moon44aac662017-02-18 02:07:01 +0900506 private boolean isNetworkInUse(String netId) {
507 return !subnets(netId).isEmpty() && !ports(netId).isEmpty();
508 }
509
510 private boolean isSubnetInUse(String subnetId) {
511 // TODO add something if needed
512 return false;
513 }
514
515 private boolean isPortInUse(String portId) {
516 // TODO add something if needed
517 return false;
518 }
519
520 private class InternalNetworkStoreDelegate implements OpenstackNetworkStoreDelegate {
521
522 @Override
523 public void notify(OpenstackNetworkEvent event) {
524 if (event != null) {
525 log.trace("send oepnstack switching event {}", event);
526 process(event);
527 }
528 }
529 }
530}