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