cb6a2fac1f24579f350575308c59da6ef74463cc
[so.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Modifications Copyright (C) 2018 IBM.
9  * Modifications Copyright (c) 2019 Samsung
10  * ================================================================================
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.so.adapters.network;
26
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Optional;
32 import javax.jws.WebService;
33 import javax.xml.ws.Holder;
34 import org.apache.commons.lang3.mutable.MutableBoolean;
35 import org.onap.logging.filter.base.ErrorCode;
36 import org.onap.so.adapters.network.beans.ContrailPolicyRef;
37 import org.onap.so.adapters.network.beans.ContrailPolicyRefSeq;
38 import org.onap.so.adapters.network.beans.ContrailSubnet;
39 import org.onap.so.adapters.network.exceptions.NetworkException;
40 import org.onap.so.adapters.network.mappers.ContrailSubnetMapper;
41 import org.onap.so.cloud.CloudConfig;
42 import org.onap.so.db.catalog.beans.CloudSite;
43 import org.onap.so.db.catalog.beans.CollectionNetworkResourceCustomization;
44 import org.onap.so.db.catalog.beans.HeatTemplate;
45 import org.onap.so.db.catalog.beans.NetworkResource;
46 import org.onap.so.db.catalog.beans.NetworkResourceCustomization;
47 import org.onap.so.db.catalog.data.repository.CollectionNetworkResourceCustomizationRepository;
48 import org.onap.so.db.catalog.data.repository.NetworkResourceCustomizationRepository;
49 import org.onap.so.db.catalog.data.repository.NetworkResourceRepository;
50 import org.onap.so.db.catalog.utils.MavenLikeVersioning;
51 import org.onap.so.entity.MsoRequest;
52 import org.onap.so.logger.LoggingAnchor;
53 import org.onap.so.logger.MessageEnum;
54 import org.onap.so.openstack.beans.HeatStatus;
55 import org.onap.so.openstack.beans.NetworkInfo;
56 import org.onap.so.openstack.beans.NetworkRollback;
57 import org.onap.so.openstack.beans.Pool;
58 import org.onap.so.openstack.beans.RouteTarget;
59 import org.onap.so.openstack.beans.StackInfo;
60 import org.onap.so.openstack.beans.Subnet;
61 import org.onap.so.openstack.exceptions.MsoAdapterException;
62 import org.onap.so.openstack.exceptions.MsoException;
63 import org.onap.so.openstack.exceptions.MsoExceptionCategory;
64 import org.onap.so.openstack.utils.MsoCommonUtils;
65 import org.onap.so.openstack.utils.MsoHeatUtils;
66 import org.onap.so.openstack.utils.MsoHeatUtilsWithUpdate;
67 import org.onap.so.openstack.utils.MsoNeutronUtils;
68 import org.onap.so.openstack.utils.MsoNeutronUtils.NetworkType;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71 import org.springframework.beans.factory.annotation.Autowired;
72 import org.springframework.core.env.Environment;
73 import org.springframework.stereotype.Component;
74 import org.springframework.transaction.annotation.Transactional;
75 import com.fasterxml.jackson.databind.JsonNode;
76 import com.fasterxml.jackson.databind.ObjectMapper;
77
78 @Component
79 @Transactional
80 @WebService(serviceName = "NetworkAdapter", endpointInterface = "org.onap.so.adapters.network.MsoNetworkAdapter",
81         targetNamespace = "http://org.onap.so/network")
82 public class MsoNetworkAdapterImpl {
83
84     private static final String OS3_NW_PROPERTY = "org.onap.so.adapters.network.aic3nw";
85     private static final String OS3_NW = "OS::ContrailV2::VirtualNetwork";
86     private static final String VLANS = "vlans";
87     private static final String PHYSICAL_NETWORK = "physical_network";
88     private static final String UPDATE_NETWORK_CONTEXT = "UpdateNetwork";
89     private static final String CREATE_NETWORK_CONTEXT = "CreateNetwork";
90     private static final String NEUTRON_MODE = "NEUTRON";
91     private static final String CLOUD_OWNER = "CloudOwner";
92     private static final String LOG_DEBUG_MSG = "Got Network definition from Catalog: {}";
93     private static final String NETWORK_EXIST_STATUS_MESSAGE =
94             "The network was found to already exist, thus no new network was created in the cloud via this request";
95     private static final String NETWORK_CREATED_STATUS_MESSAGE =
96             "The new network was successfully created in the cloud";
97     private static final String NETWORK_NOT_EXIST_STATUS_MESSAGE =
98             "The network was not found, thus no network was deleted in the cloud via this request";
99     private static final String NETWORK_DELETED_STATUS_MESSAGE = "The network was successfully deleted in the cloud";
100
101     private static final Logger logger = LoggerFactory.getLogger(MsoNetworkAdapterImpl.class);
102
103     @Autowired
104     private CloudConfig cloudConfig;
105     @Autowired
106     private Environment environment;
107     @Autowired
108     private MsoNeutronUtils neutron;
109     @Autowired
110     private MsoHeatUtils heat;
111     @Autowired
112     private MsoHeatUtilsWithUpdate heatWithUpdate;
113     @Autowired
114     private MsoCommonUtils commonUtils;
115
116     @Autowired
117     private NetworkResourceCustomizationRepository networkCustomRepo;
118
119     @Autowired
120     private CollectionNetworkResourceCustomizationRepository collectionNetworkCustomRepo;
121
122     @Autowired
123     private NetworkResourceRepository networkResourceRepo;
124
125     public MsoNetworkAdapterImpl() {}
126
127     /**
128      * This is the "Create Network" web service implementation. It will create a new Network of the requested type in
129      * the specified cloud and tenant. The tenant must exist at the time this service is called.
130      *
131      * If a network with the same name already exists, this can be considered a success or failure, depending on the
132      * value of the 'failIfExists' parameter.
133      *
134      * There will be a pre-defined set of network types defined in the MSO Catalog. All such networks will have a
135      * similar configuration, based on the allowable Openstack networking definitions. This includes basic networks,
136      * provider networks (with a single VLAN), and multi-provider networks (one or more VLANs)
137      *
138      * Initially, all provider networks must be "vlan" type, and multiple segments in a multi-provider network must be
139      * multiple VLANs on the same physical network.
140      *
141      * This service supports two modes of Network creation/update: - via Heat Templates - via Neutron API The network
142      * orchestration mode for each network type is declared in its catalog definition. All Heat-based templates must
143      * support some subset of the same input parameters: network_name, physical_network, vlan(s).
144      *
145      * The method returns the network ID and a NetworkRollback object. This latter object can be passed as-is to the
146      * rollbackNetwork operation to undo everything that was created. This is useful if a network is successfully
147      * created but the orchestration fails on a subsequent operation.
148      */
149
150     public void createNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
151             String networkName, String physicalNetworkName, List<Integer> vlans, List<RouteTarget> routeTargets,
152             String shared, String external, Boolean failIfExists, Boolean backout, List<Subnet> subnets,
153             List<String> policyFqdns, List<String> routeTableFqdns, MsoRequest msoRequest, Holder<String> stackId,
154             MutableBoolean isOs3Nw) throws NetworkException {
155         logger.debug("*** CREATE Network: {} of type {} in {}/{}", networkName, networkType, cloudSiteId, tenantId);
156
157         // Will capture execution time for metrics
158         long startTime = System.currentTimeMillis();
159
160         Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
161         if (!cloudSiteOpt.isPresent()) {
162             String error = String.format(
163                     "Configuration Error. Stack %s in %s/%s: CloudSite does not exist in MSO Configuration",
164                     networkName, cloudSiteId, tenantId);
165             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
166             // Set the detailed error as the Exception 'message'
167             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
168         }
169
170
171         NetworkResource networkResource = networkCheck(startTime, networkType, modelCustomizationUuid, networkName,
172                 physicalNetworkName, vlans, routeTargets, cloudSiteId, cloudSiteOpt.get());
173         NetworkType neutronNetworkType = NetworkType.valueOf(networkResource.getNeutronNetworkType());
174
175         HeatTemplate heatTemplate = networkResource.getHeatTemplate();
176         if (heatTemplate == null) {
177             String error = String.format("Network error - undefined Heat Template. Network Type = %s", networkType);
178             logger.error(LoggingAnchor.THREE, MessageEnum.RA_PARAM_NOT_FOUND, ErrorCode.DataError.getValue(), error);
179             throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
180         }
181
182         logger.debug("Got HEAT Template from DB: {}", heatTemplate);
183
184         // "Fix" the template if it has CR/LF (getting this from Oracle)
185         String template = heatTemplate.getHeatTemplate();
186         template = template.replaceAll("\r\n", "\n");
187
188         boolean os3template = false;
189
190         String os3nw = environment.getProperty(OS3_NW_PROPERTY, OS3_NW);
191
192         if (template.contains(os3nw))
193             os3template = true;
194
195         isOs3Nw.setValue(os3template);
196         // First, look up to see if the Network already exists (by name).
197         // For HEAT orchestration of networks, the stack name will always match the network name
198         StackInfo heatStack = null;
199         try {
200             heatStack = heat.queryStack(cloudSiteId, CLOUD_OWNER, tenantId, networkName);
201         } catch (MsoException me) {
202             me.addContext(CREATE_NETWORK_CONTEXT);
203             logger.error("{} {} Create Network (heat): query network {} in {}/{}: ", MessageEnum.RA_QUERY_NETWORK_EXC,
204                     ErrorCode.DataError.getValue(), networkName, cloudSiteId, tenantId, me);
205             throw new NetworkException(me);
206         }
207
208         if (heatStack != null && (heatStack.getStatus() != HeatStatus.NOTFOUND)) {
209             // Stack exists. Return success or error depending on input directive
210             if (failIfExists != null && failIfExists) {
211                 String error = String.format("CreateNetwork: Stack %s already exists in %s/%s as %s", networkName,
212                         cloudSiteId, tenantId, heatStack.getCanonicalName());
213                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_ALREADY_EXIST, ErrorCode.DataError.getValue(),
214                         error);
215                 throw new NetworkException(error, MsoExceptionCategory.USERDATA);
216             } else {
217                 logger.warn("{} {} Found Existing network stack, status={} networkName={} for {}/{}",
218                         MessageEnum.RA_NETWORK_ALREADY_EXIST, ErrorCode.DataError.getValue(), heatStack.getStatus(),
219                         networkName, cloudSiteId, tenantId);
220             }
221             heat.updateResourceStatus(msoRequest.getRequestId(), NETWORK_EXIST_STATUS_MESSAGE);
222             return;
223         }
224
225         // Ready to deploy the new Network
226         // Build the common set of HEAT template parameters
227         Map<String, Object> stackParams = populateNetworkParams(neutronNetworkType, networkName, physicalNetworkName,
228                 vlans, routeTargets, shared, external, os3template);
229
230         // Validate (and update) the input parameters against the DB definition
231         // Shouldn't happen unless DB config is wrong, since all networks use same inputs
232         // and inputs were already validated.
233         try {
234             stackParams = heat.validateStackParams(stackParams, heatTemplate);
235         } catch (IllegalArgumentException e) {
236             String error = "Create Network: Configuration Error: " + e.getMessage();
237             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error, e);
238             // Input parameters were not valid
239             throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
240         }
241
242         if (subnets != null) {
243             try {
244                 if (os3template) {
245                     template = mergeSubnetsAIC3(template, subnets, stackParams);
246                 } else {
247                     template = mergeSubnets(template, subnets);
248                 }
249             } catch (MsoException me) {
250                 me.addContext(CREATE_NETWORK_CONTEXT);
251                 logger.error("{} {} Exception Create Network, merging subnets for network (heat) type {} in {}/{} ",
252                         MessageEnum.RA_CREATE_NETWORK_EXC, ErrorCode.DataError.getValue(),
253                         neutronNetworkType.toString(), cloudSiteId, tenantId, me);
254                 throw new NetworkException(me);
255             }
256         }
257
258         if (policyFqdns != null && !policyFqdns.isEmpty() && os3template) {
259             try {
260                 mergePolicyRefs(policyFqdns, stackParams);
261             } catch (MsoException me) {
262                 me.addContext(CREATE_NETWORK_CONTEXT);
263                 logger.error("{} {} Exception Create Network, merging policyRefs type {} in {}/{} ",
264                         MessageEnum.RA_CREATE_NETWORK_EXC, ErrorCode.DataError.getValue(),
265                         neutronNetworkType.toString(), cloudSiteId, tenantId, me);
266                 throw new NetworkException(me);
267             }
268         }
269
270         if (routeTableFqdns != null && !routeTableFqdns.isEmpty() && os3template) {
271             try {
272                 mergeRouteTableRefs(routeTableFqdns, stackParams);
273             } catch (MsoException me) {
274                 me.addContext(CREATE_NETWORK_CONTEXT);
275                 logger.error("{} {} Exception Create Network, merging routeTableRefs type {} in {}/{} ",
276                         MessageEnum.RA_CREATE_NETWORK_EXC, ErrorCode.DataError.getValue(),
277                         neutronNetworkType.toString(), cloudSiteId, tenantId, me);
278                 throw new NetworkException(me);
279             }
280         }
281
282         // Deploy the network stack
283         // Ignore MsoStackAlreadyExists exception because we already checked.
284         try {
285             if (backout == null)
286                 backout = true;
287             heatStack = heat.createStack(cloudSiteId, CLOUD_OWNER, tenantId, networkName, null, template, stackParams,
288                     false, heatTemplate.getTimeoutMinutes(), null, null, null, backout.booleanValue(), failIfExists);
289         } catch (MsoException me) {
290             me.addContext(CREATE_NETWORK_CONTEXT);
291             logger.error("{} {} Exception creating network type {} in {}/{} ", MessageEnum.RA_CREATE_NETWORK_EXC,
292                     ErrorCode.DataError.getValue(), networkName, cloudSiteId, tenantId, me);
293             throw new NetworkException(me);
294         }
295
296         // Reach this point if createStack is successful.
297         stackId.value = heatStack.getCanonicalName();
298
299         try {
300             heat.updateResourceStatus(msoRequest.getRequestId(), NETWORK_CREATED_STATUS_MESSAGE);
301         } catch (Exception e) {
302             logger.warn("Exception while updating infra active request", e);
303         }
304
305         logger.debug("Network {} successfully created via HEAT", networkName);
306
307         return;
308     }
309
310     /**
311      * This is the "Update Network" web service implementation. It will update an existing Network of the requested type
312      * in the specified cloud and tenant. The typical use will be to replace the VLANs with the supplied list (to add or
313      * remove a VLAN), but other properties may be updated as well.
314      *
315      * There will be a pre-defined set of network types defined in the MSO Catalog. All such networks will have a
316      * similar configuration, based on the allowable Openstack networking definitions. This includes basic networks,
317      * provider networks (with a single VLAN), and multi-provider networks (one or more VLANs).
318      *
319      * Initially, all provider networks must currently be "vlan" type, and multi-provider networks must be multiple
320      * VLANs on the same physical network.
321      *
322      * This service supports two modes of Network update: - via Heat Templates - via Neutron API The network
323      * orchestration mode for each network type is declared in its catalog definition. All Heat-based templates must
324      * support some subset of the same input parameters: network_name, physical_network, vlan, segments.
325      *
326      * The method returns a NetworkRollback object. This object can be passed as-is to the rollbackNetwork operation to
327      * undo everything that was updated. This is useful if a network is successfully updated but orchestration fails on
328      * a subsequent operation.
329      */
330     public void updateNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
331             String networkId, String networkName, String physicalNetworkName, List<Integer> vlans,
332             List<RouteTarget> routeTargets, String shared, String external, List<Subnet> subnets,
333             List<String> policyFqdns, List<String> routeTableFqdns, MsoRequest msoRequest, Holder<String> stackId)
334             throws NetworkException {
335
336         logger.debug("***UPDATE Network adapter with Network: {} of type {} in {}/{}", networkName, networkType,
337                 cloudSiteId, tenantId);
338
339         // Will capture execution time for metrics
340         long startTime = System.currentTimeMillis();
341
342         Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
343         if (!cloudSiteOpt.isPresent()) {
344             String error = String.format(
345                     "UpdateNetwork: Configuration Error. Stack %s in %s/%s: CloudSite does not exist in MSO Configuration",
346                     networkName, cloudSiteId, tenantId);
347             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
348             // Set the detailed error as the Exception 'message'
349             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
350         }
351
352         NetworkResource networkResource = networkCheck(startTime, networkType, modelCustomizationUuid, networkName,
353                 physicalNetworkName, vlans, routeTargets, cloudSiteId, cloudSiteOpt.get());
354         String mode = networkResource.getOrchestrationMode();
355         NetworkType neutronNetworkType = NetworkType.valueOf(networkResource.getNeutronNetworkType());
356
357         // Use an MsoNeutronUtils for all Neutron commands
358
359         if (NEUTRON_MODE.equals(mode)) {
360
361             // Verify that the Network exists
362             // For Neutron-based orchestration, the networkId is the Neutron Network UUID.
363             NetworkInfo netInfo = null;
364             try {
365                 netInfo = neutron.queryNetwork(networkId, tenantId, cloudSiteId);
366             } catch (MsoException me) {
367                 me.addContext(UPDATE_NETWORK_CONTEXT);
368                 logger.error("{} {} Exception - queryNetwork query {} in {}/{} ", MessageEnum.RA_QUERY_NETWORK_EXC,
369                         ErrorCode.BusinessProcessError.getValue(), networkId, cloudSiteId, tenantId, me);
370                 throw new NetworkException(me);
371             }
372
373             if (netInfo == null) {
374                 String error = String.format("Update Nework: Network %s does not exist in %s/%s", networkId,
375                         cloudSiteId, tenantId);
376                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_NOT_FOUND,
377                         ErrorCode.BusinessProcessError.getValue(), error);
378                 // Does not exist. Throw an exception (can't update a non-existent network)
379                 throw new NetworkException(error, MsoExceptionCategory.USERDATA);
380             }
381             try {
382                 netInfo = neutron.updateNetwork(cloudSiteId, tenantId, networkId, neutronNetworkType,
383                         physicalNetworkName, vlans);
384             } catch (MsoException me) {
385                 me.addContext(UPDATE_NETWORK_CONTEXT);
386                 logger.error("{} {} Exception - updateNetwork {} in {}/{} ", MessageEnum.RA_UPDATE_NETWORK_ERR,
387                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
388                 throw new NetworkException(me);
389             }
390
391             logger.debug("Network {} updated, id = {}", networkId, netInfo.getId());
392         } else if ("HEAT".equals(mode)) {
393
394             // First, look up to see that the Network already exists.
395             // For Heat-based orchestration, the networkId is the network Stack ID.
396             StackInfo heatStack = null;
397             try {
398                 heatStack = heat.queryStack(cloudSiteId, CLOUD_OWNER, tenantId, networkName);
399             } catch (MsoException me) {
400                 me.addContext(UPDATE_NETWORK_CONTEXT);
401                 logger.error("{} {} Exception - QueryStack query {} in {}/{} ", MessageEnum.RA_QUERY_NETWORK_EXC,
402                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
403                 throw new NetworkException(me);
404             }
405
406             if (heatStack == null || (heatStack.getStatus() == HeatStatus.NOTFOUND)) {
407                 String error = String.format("UpdateNetwork: Stack %s does not exist in %s/%s", networkName,
408                         cloudSiteId, tenantId);
409                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_NOT_FOUND, ErrorCode.DataError.getValue(),
410                         error);
411                 // Network stack does not exist. Return an error
412                 throw new NetworkException(error, MsoExceptionCategory.USERDATA);
413             }
414
415             // Get the previous parameters for rollback
416             Map<String, Object> heatParams = heatStack.getParameters();
417
418             List<Integer> previousVlans = new ArrayList<>();
419             String vlansParam = (String) heatParams.get(VLANS);
420             if (vlansParam != null) {
421                 for (String vlan : vlansParam.split(",")) {
422                     try {
423                         previousVlans.add(Integer.parseInt(vlan));
424                     } catch (NumberFormatException e) {
425                         logger.warn("{} {} Exception - VLAN parse for params {} ", MessageEnum.RA_VLAN_PARSE,
426                                 ErrorCode.DataError.getValue(), vlansParam, e);
427                     }
428                 }
429             }
430             logger.debug("Update Stack:  Previous VLANS: {}", previousVlans);
431
432             // Ready to deploy the updated Network via Heat
433             HeatTemplate heatTemplate = networkResource.getHeatTemplate();
434             if (heatTemplate == null) {
435                 String error = "Network error - undefined Heat Template. Network Type=" + networkType;
436                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_PARAM_NOT_FOUND, ErrorCode.DataError.getValue(),
437                         error);
438                 throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
439             }
440
441             logger.debug("Got HEAT Template from DB: {}", heatTemplate);
442
443             // "Fix" the template if it has CR/LF (getting this from Oracle)
444             String template = heatTemplate.getHeatTemplate();
445             template = template.replaceAll("\r\n", "\n");
446
447             boolean os3template = false;
448             String os3nw = OS3_NW;
449
450             os3nw = environment.getProperty(OS3_NW_PROPERTY, OS3_NW);
451
452             if (template.contains(os3nw))
453                 os3template = true;
454
455             // Build the common set of HEAT template parameters
456             Map<String, Object> stackParams = populateNetworkParams(neutronNetworkType, networkName,
457                     physicalNetworkName, vlans, routeTargets, shared, external, os3template);
458
459             // Validate (and update) the input parameters against the DB definition
460             // Shouldn't happen unless DB config is wrong, since all networks use same inputs
461             try {
462                 stackParams = heat.validateStackParams(stackParams, heatTemplate);
463             } catch (IllegalArgumentException e) {
464                 String error = "UpdateNetwork: Configuration Error: Network Type=" + networkType;
465                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
466                 throw new NetworkException(error, MsoExceptionCategory.INTERNAL, e);
467             }
468
469             if (subnets != null) {
470                 try {
471                     if (os3template) {
472                         template = mergeSubnetsAIC3(template, subnets, stackParams);
473                     } else {
474                         template = mergeSubnets(template, subnets);
475                     }
476                 } catch (MsoException me) {
477                     me.addContext(UPDATE_NETWORK_CONTEXT);
478                     logger.error("{} {} Exception - UpdateNetwork mergeSubnets for network type {} in {}/{} ",
479                             MessageEnum.RA_UPDATE_NETWORK_ERR, ErrorCode.DataError.getValue(),
480                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
481                     throw new NetworkException(me);
482                 }
483             }
484
485             if (policyFqdns != null && os3template) {
486                 try {
487                     mergePolicyRefs(policyFqdns, stackParams);
488                 } catch (MsoException me) {
489                     me.addContext(UPDATE_NETWORK_CONTEXT);
490                     logger.error("{} {} Exception - UpdateNetwork mergePolicyRefs type {} in {}/{} ",
491                             MessageEnum.RA_UPDATE_NETWORK_ERR, ErrorCode.DataError.getValue(),
492                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
493                     throw new NetworkException(me);
494                 }
495             }
496
497             if (routeTableFqdns != null && !routeTableFqdns.isEmpty() && os3template) {
498                 try {
499                     mergeRouteTableRefs(routeTableFqdns, stackParams);
500                 } catch (MsoException me) {
501                     me.addContext(UPDATE_NETWORK_CONTEXT);
502                     logger.error("{} {} Exception - UpdateNetwork mergeRouteTableRefs type {} in {}/{} ",
503                             MessageEnum.RA_UPDATE_NETWORK_ERR, ErrorCode.DataError.getValue(),
504                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
505                     throw new NetworkException(me);
506                 }
507             }
508
509             // Update the network stack
510             // Ignore MsoStackNotFound exception because we already checked.
511             try {
512                 heatStack = heatWithUpdate.updateStack(cloudSiteId, CLOUD_OWNER, tenantId, networkId, template,
513                         stackParams, false, heatTemplate.getTimeoutMinutes());
514             } catch (MsoException me) {
515                 me.addContext(UPDATE_NETWORK_CONTEXT);
516                 logger.error("{} {} Exception - update network {} in {}/{} ", MessageEnum.RA_UPDATE_NETWORK_ERR,
517                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
518                 throw new NetworkException(me);
519             }
520
521             stackId.value = heatStack.getCanonicalName();
522
523             logger.debug("Network {} successfully updated via HEAT", networkId);
524         }
525
526         return;
527     }
528
529     private NetworkResource networkCheck(long startTime, String networkType, String modelCustomizationUuid,
530             String networkName, String physicalNetworkName, List<Integer> vlans, List<RouteTarget> routeTargets,
531             String cloudSiteId, CloudSite cloudSite) throws NetworkException {
532         // Retrieve the Network Resource definition
533         NetworkResource networkResource = null;
534         NetworkResourceCustomization networkCust = null;
535         CollectionNetworkResourceCustomization collectionNetworkCust = null;
536         if (commonUtils.isNullOrEmpty(modelCustomizationUuid)) {
537             if (!commonUtils.isNullOrEmpty(networkType)) {
538                 networkResource = networkResourceRepo.findFirstByModelNameOrderByModelVersionDesc(networkType);
539             }
540         } else {
541             networkCust = networkCustomRepo.findOneByModelCustomizationUUID(modelCustomizationUuid);
542             if (networkCust == null) {
543                 collectionNetworkCust =
544                         collectionNetworkCustomRepo.findOneByModelCustomizationUUID(modelCustomizationUuid);
545             }
546         }
547         if (networkCust != null) {
548             logger.debug("Got Network Customization definition from Catalog: {}", networkCust);
549
550             networkResource = networkCust.getNetworkResource();
551         } else if (collectionNetworkCust != null) {
552             logger.debug("Retrieved Collection Network Resource Customization from Catalog: {}", collectionNetworkCust);
553             networkResource = collectionNetworkCust.getNetworkResource();
554         }
555         if (networkResource == null) {
556             String error = String.format(
557                     "Create/UpdateNetwork: Unable to get network resource with NetworkType: %s or ModelCustomizationUUID:%s",
558                     networkType, modelCustomizationUuid);
559             logger.error(LoggingAnchor.THREE, MessageEnum.RA_UNKOWN_PARAM, ErrorCode.DataError.getValue(), error);
560
561             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
562         }
563         logger.debug(LOG_DEBUG_MSG, networkResource);
564
565         String mode = networkResource.getOrchestrationMode();
566         NetworkType neutronNetworkType = NetworkType.valueOf(networkResource.getNeutronNetworkType());
567
568         // All Networks are orchestrated via HEAT or Neutron
569         if (!("HEAT".equals(mode) || NEUTRON_MODE.equals(mode))) {
570             String error = "CreateNetwork: Configuration Error: Network Type = " + networkType;
571             logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_ORCHE_MODE_NOT_SUPPORT,
572                     ErrorCode.DataError.getValue(), error);
573             throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
574         }
575
576         MavenLikeVersioning osV = new MavenLikeVersioning();
577         osV.setVersion(cloudSite.getCloudVersion());
578         if ((osV.isMoreRecentThan(networkResource.getAicVersionMin())
579                 || osV.isTheSameVersion(networkResource.getAicVersionMin())) // os
580                 // >=
581                 // min
582                 && (osV.isTheSameVersion(networkResource.getAicVersionMax())
583                         || !(osV.isMoreRecentThan(networkResource.getAicVersionMax())))) // os <= max
584         {
585             logger.debug("Network Type:{} VersionMin:{} VersionMax:{} supported on Cloud:{} with AIC_Version:{}",
586                     networkType, networkResource.getAicVersionMin(), networkResource.getAicVersionMax(), cloudSiteId,
587                     cloudSite.getCloudVersion());
588         } else {
589             String error = String.format(
590                     "Network Type:%s Version_Min:%s Version_Max:%s not supported on Cloud:%s with AIC_Version:%s",
591                     networkType, networkResource.getAicVersionMin(), networkResource.getAicVersionMax(), cloudSiteId,
592                     cloudSite.getCloudVersion());
593             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
594             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
595         }
596
597         // Validate the Network parameters.
598         String missing =
599                 validateNetworkParams(neutronNetworkType, networkName, physicalNetworkName, vlans, routeTargets);
600         if (!missing.isEmpty()) {
601             String error = "Create Network: Missing parameters: " + missing;
602             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
603
604             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
605         }
606
607         return networkResource;
608     }
609
610     /**
611      * This is the "Delete Network" web service implementation. It will delete a Network in the specified cloud and
612      * tenant.
613      *
614      * If the network is not found, it is treated as a success.
615      *
616      * This service supports two modes of Network creation/update/delete: - via Heat Templates - via Neutron API The
617      * network orchestration mode for each network type is declared in its catalog definition.
618      *
619      * For Heat-based orchestration, the networkId should be the stack ID. For Neutron-based orchestration, the
620      * networkId should be the Neutron network UUID.
621      *
622      * The method returns nothing on success. Rollback is not possible for delete commands, so any failure on delete
623      * will require manual fallout in the client.
624      */
625     public void deleteNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
626             String networkId, MsoRequest msoRequest) throws NetworkException {
627         logger.debug("*** DELETE Network adapter with Network: {} in {}/{}", networkId, cloudSiteId, tenantId);
628         if (commonUtils.isNullOrEmpty(cloudSiteId) || commonUtils.isNullOrEmpty(tenantId)
629                 || commonUtils.isNullOrEmpty(networkId)) {
630             String error = "Missing mandatory parameter cloudSiteId, tenantId or networkId";
631             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
632             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
633         }
634
635         int timeoutMinutes = heat.getNetworkHeatTimeoutValue(modelCustomizationUuid, networkType);
636
637         boolean networkDeleted = false;
638         try {
639             StackInfo stack = heat.deleteStack(tenantId, CLOUD_OWNER, cloudSiteId, networkId, false, timeoutMinutes);
640             networkDeleted = stack.isOperationPerformed();
641         } catch (MsoException me) {
642             me.addContext("DeleteNetwork");
643             logger.error("{} {} Delete Network (heat): {} in {}/{} ", MessageEnum.RA_DELETE_NETWORK_EXC,
644                     ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
645             throw new NetworkException(me);
646         }
647
648         try {
649             heat.updateResourceStatus(msoRequest.getRequestId(),
650                     networkDeleted ? NETWORK_DELETED_STATUS_MESSAGE : NETWORK_NOT_EXIST_STATUS_MESSAGE);
651         } catch (Exception e) {
652             logger.warn("Exception while updating infra active request", e);
653         }
654     }
655
656     /**
657      * This web service endpoint will rollback a previous Create VNF operation. A rollback object is returned to the
658      * client in a successful creation response. The client can pass that object as-is back to the rollbackVnf operation
659      * to undo the creation.
660      *
661      * The rollback includes removing the VNF and deleting the tenant if the tenant did not exist prior to the VNF
662      * creation.
663      */
664     public void rollbackNetwork(NetworkRollback rollback) throws NetworkException {
665         if (rollback == null) {
666             logger.error("{} {} rollback is null", MessageEnum.RA_ROLLBACK_NULL, ErrorCode.DataError.getValue());
667             return;
668         }
669
670         // Get the elements of the VnfRollback object for easier access
671         String cloudSiteId = rollback.getCloudId();
672         String tenantId = rollback.getTenantId();
673         String networkId = rollback.getNetworkStackId();
674
675         logger.debug("*** ROLLBACK Network {} in {}/{}", networkId, cloudSiteId, tenantId);
676
677         if (rollback.getNetworkCreated()) {
678             try {
679                 heat.deleteStack(tenantId, CLOUD_OWNER, cloudSiteId, networkId, false, 120);
680             } catch (MsoException me) {
681                 me.addContext("RollbackNetwork");
682                 logger.error("{} {} Exception - Rollback Network (heat): {} in {}/{} ",
683                         MessageEnum.RA_DELETE_NETWORK_EXC, ErrorCode.BusinessProcessError.getValue(), networkId,
684                         cloudSiteId, tenantId, me);
685                 throw new NetworkException(me);
686             }
687
688         }
689     }
690
691     private String validateNetworkParams(NetworkType neutronNetworkType, String networkName, String physicalNetwork,
692             List<Integer> vlans, List<RouteTarget> routeTargets) {
693         String sep = "";
694         StringBuilder missing = new StringBuilder();
695         if (commonUtils.isNullOrEmpty(networkName)) {
696             missing.append("networkName");
697             sep = ",";
698         }
699
700         if (neutronNetworkType == NetworkType.PROVIDER || neutronNetworkType == NetworkType.MULTI_PROVIDER) {
701             if (commonUtils.isNullOrEmpty(physicalNetwork)) {
702                 missing.append(sep).append("physicalNetworkName");
703                 sep = ",";
704             }
705             if (vlans == null || vlans.isEmpty()) {
706                 missing.append(sep).append(VLANS);
707             }
708         }
709
710         return missing.toString();
711     }
712
713     private Map<String, Object> populateNetworkParams(NetworkType neutronNetworkType, String networkName,
714             String physicalNetwork, List<Integer> vlans, List<RouteTarget> routeTargets, String shared, String external,
715             boolean os3template) {
716         // Build the common set of HEAT template parameters
717         Map<String, Object> stackParams = new HashMap<>();
718         stackParams.put("network_name", networkName);
719
720         if (neutronNetworkType == NetworkType.PROVIDER) {
721             // For Provider type
722             stackParams.put(PHYSICAL_NETWORK, physicalNetwork);
723             stackParams.put("vlan", vlans.get(0).toString());
724         } else if (neutronNetworkType == NetworkType.MULTI_PROVIDER) {
725             // For Multi-provider, PO supports a custom resource extension of ProviderNet.
726             // It supports all ProviderNet properties except segmentation_id, and adds a
727             // comma-separated-list of VLANs as a "segments" property.
728             // Note that this does not match the Neutron definition of Multi-Provider network,
729             // which contains a list of 'segments', each having physical_network, network_type,
730             // and segmentation_id.
731             StringBuilder buf = new StringBuilder();
732             String sep = "";
733             for (Integer vlan : vlans) {
734                 buf.append(sep).append(vlan.toString());
735                 sep = ",";
736             }
737             String csl = buf.toString();
738
739             stackParams.put(PHYSICAL_NETWORK, physicalNetwork);
740             stackParams.put(VLANS, csl);
741         }
742         if (routeTargets != null) {
743
744             String rtGlobal = "";
745             String rtImport = "";
746             String rtExport = "";
747             String sep = "";
748             for (RouteTarget rt : routeTargets) {
749                 boolean rtIsNull = false;
750                 if (rt != null) {
751                     String routeTarget = rt.getRouteTarget();
752                     String routeTargetRole = rt.getRouteTargetRole();
753                     logger.debug("Checking for an actually null route target: {}", rt);
754                     if (routeTarget == null || routeTarget.equals("") || routeTarget.equalsIgnoreCase("null"))
755                         rtIsNull = true;
756                     if (routeTargetRole == null || routeTargetRole.equals("")
757                             || routeTargetRole.equalsIgnoreCase("null"))
758                         rtIsNull = true;
759                 } else {
760                     rtIsNull = true;
761                 }
762                 if (!rtIsNull) {
763                     logger.debug("Input RT:{}", rt);
764                     String role = rt.getRouteTargetRole();
765                     String rtValue = rt.getRouteTarget();
766
767                     if ("IMPORT".equalsIgnoreCase(role)) {
768                         sep = rtImport.isEmpty() ? "" : ",";
769                         rtImport = os3template ? rtImport + sep + "target:" + rtValue : rtImport + sep + rtValue;
770                     } else if ("EXPORT".equalsIgnoreCase(role)) {
771                         sep = rtExport.isEmpty() ? "" : ",";
772                         rtExport = os3template ? rtExport + sep + "target:" + rtValue : rtExport + sep + rtValue;
773                     } else // covers BOTH, empty etc
774                     {
775                         sep = rtGlobal.isEmpty() ? "" : ",";
776                         rtGlobal = os3template ? rtGlobal + sep + "target:" + rtValue : rtGlobal + sep + rtValue;
777                     }
778
779                 }
780             }
781
782             if (!rtImport.isEmpty()) {
783                 stackParams.put("route_targets_import", rtImport);
784             }
785             if (!rtExport.isEmpty()) {
786                 stackParams.put("route_targets_export", rtExport);
787             }
788             if (!rtGlobal.isEmpty()) {
789                 stackParams.put("route_targets", rtGlobal);
790             }
791         }
792         if (commonUtils.isNullOrEmpty(shared)) {
793             stackParams.put("shared", "False");
794         } else {
795             stackParams.put("shared", shared);
796         }
797         if (commonUtils.isNullOrEmpty(external)) {
798             stackParams.put("external", "False");
799         } else {
800             stackParams.put("external", external);
801         }
802         return stackParams;
803     }
804
805
806
807     /**
808      * policyRef_list structure in stackParams [ { "network_policy_refs_data_sequence": {
809      * "network_policy_refs_data_sequence_major": "1", "network_policy_refs_data_sequence_minor": "0" } }, {
810      * "network_policy_refs_data_sequence": { "network_policy_refs_data_sequence_major": "2",
811      * "network_policy_refs_data_sequence_minor": "0" } } ]
812      **/
813     private void mergePolicyRefs(List<String> pFqdns, Map<String, Object> stackParams) throws MsoException {
814         // Resource Property
815         List<ContrailPolicyRef> prlist = new ArrayList<>();
816         int index = 1;
817
818         if (pFqdns != null) {
819             for (String pf : pFqdns) {
820                 if (!commonUtils.isNullOrEmpty(pf)) {
821                     ContrailPolicyRef pr = new ContrailPolicyRef();
822                     ContrailPolicyRefSeq refSeq = new ContrailPolicyRefSeq(String.valueOf(index), "0");
823                     pr.setSeq(refSeq);
824                     index++;
825                     logger.debug("Contrail PolicyRefs Data:{}", pr);
826                     prlist.add(pr);
827                 }
828             }
829         } else {
830             String error = "Null pFqdns at start of mergePolicyRefs";
831             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MARSHING_ERROR, ErrorCode.BusinessProcessError.getValue(),
832                     error);
833             throw new MsoAdapterException(error);
834         }
835
836         JsonNode node = null;
837         try {
838             ObjectMapper mapper = new ObjectMapper();
839             node = mapper.convertValue(prlist, JsonNode.class);
840             String jsonString = mapper.writeValueAsString(prlist);
841             logger.debug("Json PolicyRefs Data:{}", jsonString);
842         } catch (Exception e) {
843             String error = "Error creating JsonNode for policyRefs Data";
844             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MARSHING_ERROR, ErrorCode.BusinessProcessError.getValue(),
845                     error, e);
846             throw new MsoAdapterException(error);
847         }
848         // update parameters
849         if (pFqdns != null && node != null) {
850             StringBuilder buf = new StringBuilder();
851             String sep = "";
852             for (String pf : pFqdns) {
853                 if (!commonUtils.isNullOrEmpty(pf)) {
854                     buf.append(sep).append(pf);
855                     sep = ",";
856                 }
857             }
858             String csl = buf.toString();
859             stackParams.put("policy_refs", csl);
860             stackParams.put("policy_refsdata", node);
861         }
862
863         logger.debug("StackParams updated with policy refs");
864         return;
865     }
866
867     private void mergeRouteTableRefs(List<String> rtFqdns, Map<String, Object> stackParams) throws MsoException {
868
869         // update parameters
870         if (rtFqdns != null) {
871             StringBuilder buf = new StringBuilder();
872             String sep = "";
873             for (String rtf : rtFqdns) {
874                 if (!commonUtils.isNullOrEmpty(rtf)) {
875                     buf.append(sep).append(rtf);
876                     sep = ",";
877                 }
878             }
879             String csl = buf.toString();
880             stackParams.put("route_table_refs", csl);
881         }
882
883         logger.debug("StackParams updated with route_table refs");
884         return;
885     }
886
887
888     /***
889      * Subnet Output structure from Juniper { "ipam_subnets": [ { "subnet": { "ip_prefix": "10.100.1.0",
890      * "ip_prefix_len": 28 }, "addr_from_start": null, "enable_dhcp": false, "default_gateway": "10.100.1.1",
891      * "dns_nameservers": [], "dhcp_option_list": null, "subnet_uuid": "10391fbf-6b9c-4160-825d-2d018b7649cf",
892      * "allocation_pools": [ { "start": "10.100.1.3", "end": "10.100.1.5" }, { "start": "10.100.1.6", "end":
893      * "10.100.1.9" } ], "host_routes": null, "dns_server_address": "10.100.1.13", "subnet_name":
894      * "subnet_MsoNW1_692c9032-e1a2-4d64-828c-7b9a4fcc05b0" }, { "subnet": { "ip_prefix": "10.100.2.16",
895      * "ip_prefix_len": 28 }, "addr_from_start": null, "enable_dhcp": true, "default_gateway": "10.100.2.17",
896      * "dns_nameservers": [], "dhcp_option_list": null, "subnet_uuid": "c7aac5ea-66fe-443a-85f9-9c38a608c0f6",
897      * "allocation_pools": [ { "start": "10.100.2.18", "end": "10.100.2.20" } ], "host_routes": null,
898      * "dns_server_address": "10.100.2.29", "subnet_name": "subnet_MsoNW1_692c9032-e1a2-4d64-828c-7b9a4fcc05b1" } ],
899      * "host_routes": null }
900      ***/
901     private String mergeSubnetsAIC3(String heatTemplate, List<Subnet> subnets, Map<String, Object> stackParams)
902             throws MsoException {
903
904         // Resource Property
905         List<ContrailSubnet> cslist = new ArrayList<>();
906         for (Subnet subnet : subnets) {
907             logger.debug("Input Subnet:{}", subnet);
908             ContrailSubnet cs = new ContrailSubnetMapper(subnet).map();
909             logger.debug("Contrail Subnet:{}", cs);
910             cslist.add(cs);
911         }
912
913         JsonNode node = null;
914         try {
915             ObjectMapper mapper = new ObjectMapper();
916             node = mapper.convertValue(cslist, JsonNode.class);
917             String jsonString = mapper.writeValueAsString(cslist);
918             logger.debug("Json Subnet List:{}", jsonString);
919         } catch (Exception e) {
920             String error = "Error creating JsonNode from input subnets";
921             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MARSHING_ERROR, ErrorCode.DataError.getValue(), error, e);
922             throw new MsoAdapterException(error);
923         }
924         // update parameters
925         if (node != null) {
926             stackParams.put("subnet_list", node);
927         }
928         // Outputs - All subnets are in one ipam_subnets structure
929         String outputTempl = "  subnet:\n" + "    description: Openstack subnet identifier\n"
930                 + "    value: { get_attr: [network, network_ipam_refs, 0, attr]}\n";
931
932         // append outputs in heatTemplate
933         int outputsIdx = heatTemplate.indexOf("outputs:");
934         heatTemplate = insertStr(heatTemplate, outputTempl, outputsIdx + 8);
935         logger.debug("Template updated with all AIC3.0 subnets:{}", heatTemplate);
936         return heatTemplate;
937     }
938
939
940     private String mergeSubnets(String heatTemplate, List<Subnet> subnets) throws MsoException {
941
942         String resourceTempl = "  subnet_%subnetId%:\n" + "    type: OS::Neutron::Subnet\n" + "    properties:\n"
943                 + "      name: %name%\n" + "      network_id: { get_resource: network }\n" + "      cidr: %cidr%\n";
944
945         /*
946          * make these optional + "      ip_version: %ipversion%\n" + "      enable_dhcp: %enabledhcp%\n" +
947          * "      gateway_ip: %gatewayip%\n" + "      allocation_pools:\n" + "       - start: %poolstart%\n" +
948          * "         end: %poolend%\n";
949          *
950          */
951
952         String outputTempl = "  subnet_id_%subnetId%:\n" + "    description: Openstack subnet identifier\n"
953                 + "    value: {get_resource: subnet_%subnetId%}\n";
954
955         String curR;
956         String curO;
957         StringBuilder resourcesBuf = new StringBuilder();
958         StringBuilder outputsBuf = new StringBuilder();
959         for (Subnet subnet : subnets) {
960
961             // build template for each subnet
962             curR = resourceTempl;
963             if (subnet.getSubnetId() != null) {
964                 curR = curR.replace("%subnetId%", subnet.getSubnetId());
965             } else {
966                 String error = "Missing Required AAI SubnetId for subnet in HEAT Template";
967                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
968                 throw new MsoAdapterException(error);
969             }
970
971             if (subnet.getSubnetName() != null) {
972                 curR = curR.replace("%name%", subnet.getSubnetName());
973             } else {
974                 curR = curR.replace("%name%", subnet.getSubnetId());
975             }
976
977             if (subnet.getCidr() != null) {
978                 curR = curR.replace("%cidr%", subnet.getCidr());
979             } else {
980                 String error = "Missing Required cidr for subnet in HEAT Template";
981                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
982                 throw new MsoAdapterException(error);
983             }
984
985             if (subnet.getIpVersion() != null) {
986                 curR = curR + "      ip_version: " + subnet.getIpVersion() + "\n";
987             }
988             if (subnet.getEnableDHCP() != null) {
989                 curR = curR + "      enable_dhcp: " + Boolean.toString(subnet.getEnableDHCP()) + "\n";
990             }
991             if (subnet.getGatewayIp() != null && !subnet.getGatewayIp().isEmpty()) {
992                 curR = curR + "      gateway_ip: " + subnet.getGatewayIp() + "\n";
993             }
994
995             if (subnet.getAllocationPools() != null) {
996                 StringBuilder tempBuf = new StringBuilder();
997                 tempBuf.append(curR);
998                 tempBuf.append("      allocation_pools:\n");
999                 for (Pool pool : subnet.getAllocationPools()) {
1000                     if (!commonUtils.isNullOrEmpty(pool.getStart()) && !commonUtils.isNullOrEmpty(pool.getEnd())) {
1001                         tempBuf.append("       - start: ");
1002                         tempBuf.append(pool.getStart());
1003                         tempBuf.append("\n         end: ");
1004                         tempBuf.append(pool.getEnd());
1005                         tempBuf.append("\n");
1006                     }
1007                 }
1008                 curR = tempBuf.toString();
1009             }
1010
1011             resourcesBuf.append(curR);
1012
1013             curO = outputTempl;
1014             curO = curO.replace("%subnetId%", subnet.getSubnetId());
1015
1016             outputsBuf.append(curO);
1017         }
1018         // append resources and outputs in heatTemplate
1019         logger.debug("Tempate initial:{}", heatTemplate);
1020         int outputsIdx = heatTemplate.indexOf("outputs:");
1021         heatTemplate = insertStr(heatTemplate, outputsBuf.toString(), outputsIdx + 8);
1022         int resourcesIdx = heatTemplate.indexOf("resources:");
1023         heatTemplate = insertStr(heatTemplate, resourcesBuf.toString(), resourcesIdx + 10);
1024
1025         logger.debug("Template updated with all subnets:{}", heatTemplate);
1026         return heatTemplate;
1027     }
1028
1029     public Map<String, String> getSubnetUUId(String key, Map<String, Object> outputs, List<Subnet> subnets) {
1030
1031         Map<String, String> sMap = new HashMap<>();
1032
1033         try {
1034             Object obj = outputs.get(key);
1035             ObjectMapper mapper = new ObjectMapper();
1036             String jStr = mapper.writeValueAsString(obj);
1037             logger.debug("Subnet_Ipam Output JSON String:{} {}", obj.getClass(), jStr);
1038
1039             JsonNode rootNode = mapper.readTree(jStr);
1040             if (rootNode != null) {
1041                 for (JsonNode sNode : rootNode.path("ipam_subnets")) {
1042                     logger.debug("Output Subnet Node {}", sNode);
1043                     String name = sNode.path("subnet_name").textValue();
1044                     String uuid = sNode.path("subnet_uuid").textValue();
1045                     String aaiId = name; // default
1046                     // try to find aaiId for name in input subnetList
1047                     if (subnets != null) {
1048                         for (Subnet subnet : subnets) {
1049                             if (subnet != null && !commonUtils.isNullOrEmpty(subnet.getSubnetName())
1050                                     && subnet.getSubnetName().equals(name)) {
1051                                 aaiId = subnet.getSubnetId();
1052                                 break;
1053                             }
1054                         }
1055                     }
1056                     sMap.put(aaiId, uuid); // bpmn needs aaid to uuid map
1057                 }
1058             } else {
1059                 logger.error("{} {} null rootNode - cannot get subnet-uuids", MessageEnum.RA_MARSHING_ERROR,
1060                         ErrorCode.DataError.getValue());
1061             }
1062         } catch (Exception e) {
1063             logger.error("{} {} Exception getting subnet-uuids ", MessageEnum.RA_MARSHING_ERROR,
1064                     ErrorCode.DataError.getValue(), e);
1065         }
1066
1067         logger.debug("Return sMap {}", sMap);
1068         return sMap;
1069     }
1070
1071     private static String insertStr(String template, String snippet, int index) {
1072
1073         String updatedTemplate;
1074
1075         logger.debug("Index:{} Snippet:{}", index, snippet);
1076
1077         String templateBeg = template.substring(0, index);
1078         String templateEnd = template.substring(index);
1079
1080         updatedTemplate = templateBeg + "\n" + snippet + templateEnd;
1081
1082         logger.debug("Template updated with a subnet:{}", updatedTemplate);
1083         return updatedTemplate;
1084     }
1085
1086     public Map<String, String> buildSubnetMap(Map<String, Object> outputs, List<Subnet> subnets, boolean os3template) {
1087
1088         Map<String, String> sMap = new HashMap<>();
1089         for (Map.Entry<String, Object> entry : outputs.entrySet()) {
1090             String key = entry.getKey();
1091             if (key != null && key.startsWith("subnet")) {
1092                 if (os3template) // one subnet_id output
1093                 {
1094                     Map<String, String> map = getSubnetUUId(key, outputs, subnets);
1095                     sMap.putAll(map);
1096                 } else // multiples subnet_%aaid% outputs
1097                 {
1098                     String subnetUUId = (String) outputs.get(key);
1099                     sMap.put(key.substring("subnet_id_".length()), subnetUUId);
1100                 }
1101             }
1102         }
1103         return sMap;
1104     }
1105
1106 }