4728effdca6817ad60d633fd0d1dcbd6297789df
[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.onap.so.adapters.network.beans.ContrailPolicyRef;
35 import org.onap.so.adapters.network.beans.ContrailPolicyRefSeq;
36 import org.onap.so.adapters.network.beans.ContrailSubnet;
37 import org.onap.so.adapters.network.exceptions.NetworkException;
38 import org.onap.so.adapters.network.mappers.ContrailSubnetMapper;
39 import org.onap.so.cloud.CloudConfig;
40 import org.onap.so.db.catalog.beans.CloudSite;
41 import org.onap.so.db.catalog.beans.CollectionNetworkResourceCustomization;
42 import org.onap.so.db.catalog.beans.HeatTemplate;
43 import org.onap.so.db.catalog.beans.NetworkResource;
44 import org.onap.so.db.catalog.beans.NetworkResourceCustomization;
45 import org.onap.so.db.catalog.data.repository.CollectionNetworkResourceCustomizationRepository;
46 import org.onap.so.db.catalog.data.repository.NetworkResourceCustomizationRepository;
47 import org.onap.so.db.catalog.data.repository.NetworkResourceRepository;
48 import org.onap.so.db.catalog.utils.MavenLikeVersioning;
49 import org.onap.so.entity.MsoRequest;
50 import org.onap.logging.filter.base.ErrorCode;
51 import org.onap.so.logger.LoggingAnchor;
52 import org.onap.so.logger.MessageEnum;
53 import org.onap.so.openstack.beans.HeatStatus;
54 import org.onap.so.openstack.beans.NetworkInfo;
55 import org.onap.so.openstack.beans.NetworkRollback;
56 import org.onap.so.openstack.beans.NetworkStatus;
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 implements MsoNetworkAdapter {
83
84     private static final String AIC3_NW_PROPERTY = "org.onap.so.adapters.network.aic3nw";
85     private static final String AIC3_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 NETWORK_ID = "network_id";
90     private static final String NETWORK_FQDN = "network_fqdn";
91     private static final String CREATE_NETWORK_CONTEXT = "CreateNetwork";
92     private static final String NEUTRON_MODE = "NEUTRON";
93     private static final String CLOUD_OWNER = "CloudOwner";
94     private static final String LOG_DEBUG_MSG = "Got Network definition from Catalog: {}";
95     private static final String NETWORK_EXIST_STATUS_MESSAGE =
96             "The network was found to already exist, thus no new network was created in the cloud via this request";
97     private static final String NETWORK_CREATED_STATUS_MESSAGE =
98             "The new network was successfully created in the cloud";
99     private static final String NETWORK_NOT_EXIST_STATUS_MESSAGE =
100             "The network was not found, thus no network was deleted in the cloud via this request";
101     private static final String NETWORK_DELETED_STATUS_MESSAGE = "The network was successfully deleted in the cloud";
102
103     private static final Logger logger = LoggerFactory.getLogger(MsoNetworkAdapterImpl.class);
104
105     @Autowired
106     private CloudConfig cloudConfig;
107     @Autowired
108     private Environment environment;
109     @Autowired
110     private MsoNeutronUtils neutron;
111     @Autowired
112     private MsoHeatUtils heat;
113     @Autowired
114     private MsoHeatUtilsWithUpdate heatWithUpdate;
115     @Autowired
116     private MsoCommonUtils commonUtils;
117
118     @Autowired
119     private NetworkResourceCustomizationRepository networkCustomRepo;
120
121     @Autowired
122     private CollectionNetworkResourceCustomizationRepository collectionNetworkCustomRepo;
123
124     @Autowired
125     private NetworkResourceRepository networkResourceRepo;
126
127     public MsoNetworkAdapterImpl() {}
128
129     /**
130      * Health Check web method. Does nothing but return to show the adapter is deployed.
131      */
132     @Override
133     public void healthCheck() {
134         logger.debug("Health check call in Network Adapter");
135     }
136
137     /**
138      * Do not use this constructor or the msoPropertiesFactory will be NULL.
139      *
140      * @see MsoNetworkAdapterImpl#MsoNetworkAdapterImpl(MsoPropertiesFactory)
141      */
142
143     @Override
144     public void createNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
145             String networkName, String physicalNetworkName, List<Integer> vlans, String shared, String external,
146             Boolean failIfExists, Boolean backout, List<Subnet> subnets, Map<String, String> networkParams,
147             MsoRequest msoRequest, Holder<String> networkId, Holder<String> neutronNetworkId,
148             Holder<Map<String, String>> subnetIdMap, Holder<NetworkRollback> rollback) throws NetworkException {
149         Holder<String> networkFqdn = new Holder<>();
150         createNetwork(cloudSiteId, tenantId, networkType, modelCustomizationUuid, networkName, physicalNetworkName,
151                 vlans, null, shared, external, failIfExists, backout, subnets, null, null, msoRequest, networkId,
152                 neutronNetworkId, networkFqdn, subnetIdMap, rollback);
153     }
154
155     @Override
156     public void createNetworkContrail(String cloudSiteId, String tenantId, String networkType,
157             String modelCustomizationUuid, String networkName, List<RouteTarget> routeTargets, String shared,
158             String external, Boolean failIfExists, Boolean backout, List<Subnet> subnets,
159             Map<String, String> networkParams, List<String> policyFqdns, List<String> routeTableFqdns,
160             MsoRequest msoRequest, Holder<String> networkId, Holder<String> neutronNetworkId,
161             Holder<String> networkFqdn, Holder<Map<String, String>> subnetIdMap, Holder<NetworkRollback> rollback)
162             throws NetworkException {
163         createNetwork(cloudSiteId, tenantId, networkType, modelCustomizationUuid, networkName, null, null, routeTargets,
164                 shared, external, failIfExists, backout, subnets, policyFqdns, routeTableFqdns, msoRequest, networkId,
165                 neutronNetworkId, networkFqdn, subnetIdMap, rollback);
166     }
167
168     /**
169      * This is the "Create Network" web service implementation. It will create a new Network of the requested type in
170      * the specified cloud and tenant. The tenant must exist at the time this service is called.
171      *
172      * If a network with the same name already exists, this can be considered a success or failure, depending on the
173      * value of the 'failIfExists' parameter.
174      *
175      * There will be a pre-defined set of network types defined in the MSO Catalog. All such networks will have a
176      * similar configuration, based on the allowable Openstack networking definitions. This includes basic networks,
177      * provider networks (with a single VLAN), and multi-provider networks (one or more VLANs)
178      *
179      * Initially, all provider networks must be "vlan" type, and multiple segments in a multi-provider network must be
180      * multiple VLANs on the same physical network.
181      *
182      * This service supports two modes of Network creation/update: - via Heat Templates - via Neutron API The network
183      * orchestration mode for each network type is declared in its catalog definition. All Heat-based templates must
184      * support some subset of the same input parameters: network_name, physical_network, vlan(s).
185      *
186      * The method returns the network ID and a NetworkRollback object. This latter object can be passed as-is to the
187      * rollbackNetwork operation to undo everything that was created. This is useful if a network is successfully
188      * created but the orchestration fails on a subsequent operation.
189      */
190
191     private void createNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
192             String networkName, String physicalNetworkName, List<Integer> vlans, List<RouteTarget> routeTargets,
193             String shared, String external, Boolean failIfExists, Boolean backout, List<Subnet> subnets,
194             List<String> policyFqdns, List<String> routeTableFqdns, MsoRequest msoRequest, Holder<String> networkId,
195             Holder<String> neutronNetworkId, Holder<String> networkFqdn, Holder<Map<String, String>> subnetIdMap,
196             Holder<NetworkRollback> rollback) throws NetworkException {
197         logger.debug("*** CREATE Network: {} of type {} in {}/{}", networkName, networkType, cloudSiteId, tenantId);
198
199         // Will capture execution time for metrics
200         long startTime = System.currentTimeMillis();
201
202         // Build a default rollback object (no actions performed)
203         NetworkRollback networkRollback = new NetworkRollback();
204         networkRollback.setCloudId(cloudSiteId);
205         networkRollback.setTenantId(tenantId);
206         networkRollback.setMsoRequest(msoRequest);
207         networkRollback.setModelCustomizationUuid(modelCustomizationUuid);
208
209         // tenant query is not required here.
210         // If the tenant doesn't exist, the Heat calls will fail anyway (when the HeatUtils try to obtain a token).
211         // So this is just catching that error in a bit more obvious way up front.
212
213         Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
214         if (!cloudSiteOpt.isPresent()) {
215             String error = String.format(
216                     "Configuration Error. Stack %s in %s/%s: CloudSite does not exist in MSO Configuration",
217                     networkName, cloudSiteId, tenantId);
218             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
219             // Set the detailed error as the Exception 'message'
220             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
221         }
222
223
224         NetworkResource networkResource = networkCheck(startTime, networkType, modelCustomizationUuid, networkName,
225                 physicalNetworkName, vlans, routeTargets, cloudSiteId, cloudSiteOpt.get());
226         String mode = networkResource.getOrchestrationMode();
227         NetworkType neutronNetworkType = NetworkType.valueOf(networkResource.getNeutronNetworkType());
228
229         if (NEUTRON_MODE.equals(mode)) {
230
231             // Use an MsoNeutronUtils for all neutron commands
232
233             // See if the Network already exists (by name)
234             NetworkInfo netInfo = null;
235             try {
236                 netInfo = neutron.queryNetwork(networkName, tenantId, cloudSiteId);
237             } catch (MsoException me) {
238                 logger.error(
239                         "{} {} Exception while querying network {} for CloudSite {} from Tenant {} from OpenStack ",
240                         MessageEnum.RA_QUERY_NETWORK_EXC, ErrorCode.BusinessProcessError.getValue(), networkName,
241                         cloudSiteId, tenantId, me);
242                 me.addContext(CREATE_NETWORK_CONTEXT);
243                 throw new NetworkException(me);
244             }
245
246             if (netInfo != null) {
247                 // Exists. If that's OK, return success with the network ID.
248                 // Otherwise, return an exception.
249                 if (failIfExists != null && failIfExists) {
250                     String error = String.format("Create Nework: Network %s already exists in %s/%s with ID %s",
251                             networkName, cloudSiteId, tenantId, netInfo.getId());
252                     logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_ALREADY_EXIST,
253                             ErrorCode.DataError.getValue(), error);
254                     throw new NetworkException(error, MsoExceptionCategory.USERDATA);
255                 } else {
256                     // Populate the outputs from the existing network.
257                     networkId.value = netInfo.getId();
258                     neutronNetworkId.value = netInfo.getId();
259                     rollback.value = networkRollback; // Default rollback - no updates performed
260                     logger.warn("{} {} Found Existing network, status={} for Neutron mode ",
261                             MessageEnum.RA_NETWORK_ALREADY_EXIST, ErrorCode.DataError.getValue(), netInfo.getStatus());
262                 }
263                 heat.updateResourceStatus(msoRequest.getRequestId(), NETWORK_EXIST_STATUS_MESSAGE);
264                 return;
265             }
266
267             try {
268                 netInfo = neutron.createNetwork(cloudSiteId, tenantId, neutronNetworkType, networkName,
269                         physicalNetworkName, vlans);
270             } catch (MsoException me) {
271                 me.addContext(CREATE_NETWORK_CONTEXT);
272                 logger.error("{} {} Create Network: type {} in {}/{}: ", MessageEnum.RA_CREATE_NETWORK_EXC,
273                         ErrorCode.DataError.getValue(), neutronNetworkType, cloudSiteId, tenantId, me);
274
275                 throw new NetworkException(me);
276             }
277
278             // Note: ignoring MsoNetworkAlreadyExists because we already checked.
279
280             // If reach this point, network creation is successful.
281             // Since directly created via Neutron, networkId tracked by MSO is the same
282             // as the neutron network ID.
283             networkId.value = netInfo.getId();
284             neutronNetworkId.value = netInfo.getId();
285
286             networkRollback.setNetworkCreated(true);
287             networkRollback.setNetworkId(netInfo.getId());
288             networkRollback.setNeutronNetworkId(netInfo.getId());
289             networkRollback.setNetworkType(networkType);
290
291             logger.debug("Network {} created, id = {}", networkName, netInfo.getId());
292         } else if ("HEAT".equals(mode)) {
293
294             HeatTemplate heatTemplate = networkResource.getHeatTemplate();
295             if (heatTemplate == null) {
296                 String error = String.format("Network error - undefined Heat Template. Network Type = %s", networkType);
297                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_PARAM_NOT_FOUND, ErrorCode.DataError.getValue(),
298                         error);
299                 throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
300             }
301
302             logger.debug("Got HEAT Template from DB: {}", heatTemplate);
303
304             // "Fix" the template if it has CR/LF (getting this from Oracle)
305             String template = heatTemplate.getHeatTemplate();
306             template = template.replaceAll("\r\n", "\n");
307
308             boolean aic3template = false;
309             String aic3nw = AIC3_NW;
310
311             aic3nw = environment.getProperty(AIC3_NW_PROPERTY, AIC3_NW);
312
313             if (template.contains(aic3nw))
314                 aic3template = true;
315
316             // First, look up to see if the Network already exists (by name).
317             // For HEAT orchestration of networks, the stack name will always match the network name
318             StackInfo heatStack = null;
319             try {
320                 heatStack = heat.queryStack(cloudSiteId, CLOUD_OWNER, tenantId, networkName);
321             } catch (MsoException me) {
322                 me.addContext(CREATE_NETWORK_CONTEXT);
323                 logger.error("{} {} Create Network (heat): query network {} in {}/{}: ",
324                         MessageEnum.RA_QUERY_NETWORK_EXC, ErrorCode.DataError.getValue(), networkName, cloudSiteId,
325                         tenantId, me);
326                 throw new NetworkException(me);
327             }
328
329             if (heatStack != null && (heatStack.getStatus() != HeatStatus.NOTFOUND)) {
330                 // Stack exists. Return success or error depending on input directive
331                 if (failIfExists != null && failIfExists) {
332                     String error = String.format("CreateNetwork: Stack %s already exists in %s/%s as %s", networkName,
333                             cloudSiteId, tenantId, heatStack.getCanonicalName());
334                     logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_ALREADY_EXIST,
335                             ErrorCode.DataError.getValue(), error);
336                     throw new NetworkException(error, MsoExceptionCategory.USERDATA);
337                 } else {
338                     // Populate the outputs from the existing stack.
339                     networkId.value = heatStack.getCanonicalName();
340                     Map<String, String> sMap = new HashMap<>();
341                     if (heatStack.getOutputs() != null) {
342                         neutronNetworkId.value = (String) heatStack.getOutputs().get(NETWORK_ID);
343                         rollback.value = networkRollback; // Default rollback - no updates performed
344                         if (aic3template) {
345                             networkFqdn.value = (String) heatStack.getOutputs().get(NETWORK_FQDN);
346                         }
347                         Map<String, Object> outputs = heatStack.getOutputs();
348
349                         for (Map.Entry<String, Object> entry : outputs.entrySet()) {
350                             String key = entry.getKey();
351                             if (key != null && key.startsWith("subnet")) {
352                                 if (aic3template) // one subnet_id output
353                                 {
354                                     Map<String, String> map = getSubnetUUId(key, outputs, subnets);
355                                     sMap.putAll(map);
356                                 } else // multiples subnet_%aaid% outputs
357                                 {
358                                     String subnetUUId = (String) outputs.get(key);
359                                     sMap.put(key.substring("subnet_id_".length()), subnetUUId);
360                                 }
361                             }
362                         }
363                     }
364                     subnetIdMap.value = sMap;
365                     logger.warn("{} {} Found Existing network stack, status={} networkName={} for {}/{}",
366                             MessageEnum.RA_NETWORK_ALREADY_EXIST, ErrorCode.DataError.getValue(), heatStack.getStatus(),
367                             networkName, cloudSiteId, tenantId);
368                 }
369                 heat.updateResourceStatus(msoRequest.getRequestId(), NETWORK_EXIST_STATUS_MESSAGE);
370                 return;
371             }
372
373             // Ready to deploy the new Network
374             // Build the common set of HEAT template parameters
375             Map<String, Object> stackParams = populateNetworkParams(neutronNetworkType, networkName,
376                     physicalNetworkName, vlans, routeTargets, shared, external, aic3template);
377
378             // Validate (and update) the input parameters against the DB definition
379             // Shouldn't happen unless DB config is wrong, since all networks use same inputs
380             // and inputs were already validated.
381             try {
382                 stackParams = heat.validateStackParams(stackParams, heatTemplate);
383             } catch (IllegalArgumentException e) {
384                 String error = "Create Network: Configuration Error: " + e.getMessage();
385                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error, e);
386                 // Input parameters were not valid
387                 throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
388             }
389
390             if (subnets != null) {
391                 try {
392                     if (aic3template) {
393                         template = mergeSubnetsAIC3(template, subnets, stackParams);
394                     } else {
395                         template = mergeSubnets(template, subnets);
396                     }
397                 } catch (MsoException me) {
398                     me.addContext(CREATE_NETWORK_CONTEXT);
399                     logger.error("{} {} Exception Create Network, merging subnets for network (heat) type {} in {}/{} ",
400                             MessageEnum.RA_CREATE_NETWORK_EXC, ErrorCode.DataError.getValue(),
401                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
402                     throw new NetworkException(me);
403                 }
404             }
405
406             if (policyFqdns != null && !policyFqdns.isEmpty() && aic3template) {
407                 try {
408                     mergePolicyRefs(policyFqdns, stackParams);
409                 } catch (MsoException me) {
410                     me.addContext(CREATE_NETWORK_CONTEXT);
411                     logger.error("{} {} Exception Create Network, merging policyRefs type {} in {}/{} ",
412                             MessageEnum.RA_CREATE_NETWORK_EXC, ErrorCode.DataError.getValue(),
413                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
414                     throw new NetworkException(me);
415                 }
416             }
417
418             if (routeTableFqdns != null && !routeTableFqdns.isEmpty() && aic3template) {
419                 try {
420                     mergeRouteTableRefs(routeTableFqdns, stackParams);
421                 } catch (MsoException me) {
422                     me.addContext(CREATE_NETWORK_CONTEXT);
423                     logger.error("{} {} Exception Create Network, merging routeTableRefs type {} in {}/{} ",
424                             MessageEnum.RA_CREATE_NETWORK_EXC, ErrorCode.DataError.getValue(),
425                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
426                     throw new NetworkException(me);
427                 }
428             }
429
430             // Deploy the network stack
431             // Ignore MsoStackAlreadyExists exception because we already checked.
432             try {
433                 if (backout == null)
434                     backout = true;
435                 heatStack = heat.createStack(cloudSiteId, CLOUD_OWNER, tenantId, networkName, null, template,
436                         stackParams, true, heatTemplate.getTimeoutMinutes(), null, null, null, backout.booleanValue(),
437                         failIfExists);
438             } catch (MsoException me) {
439                 me.addContext(CREATE_NETWORK_CONTEXT);
440                 logger.error("{} {} Exception creating network type {} in {}/{} ", MessageEnum.RA_CREATE_NETWORK_EXC,
441                         ErrorCode.DataError.getValue(), networkName, cloudSiteId, tenantId, me);
442                 throw new NetworkException(me);
443             }
444
445             // Reach this point if createStack is successful.
446
447             // For Heat-based orchestration, the MSO-tracked network ID is the heat stack,
448             // and the neutronNetworkId is the network UUID returned in stack outputs.
449             networkId.value = heatStack.getCanonicalName();
450             if (heatStack.getOutputs() != null) {
451                 neutronNetworkId.value = (String) heatStack.getOutputs().get(NETWORK_ID);
452                 if (aic3template) {
453                     networkFqdn.value = (String) heatStack.getOutputs().get(NETWORK_FQDN);
454                 }
455             }
456             Map<String, Object> outputs = heatStack.getOutputs();
457             Map<String, String> sMap = new HashMap<>();
458             if (outputs != null) {
459                 for (Map.Entry<String, Object> entry : outputs.entrySet()) {
460                     String key = entry.getKey();
461                     if (key != null && key.startsWith("subnet")) {
462                         if (aic3template) // one subnet output expected
463                         {
464                             Map<String, String> map = getSubnetUUId(key, outputs, subnets);
465                             sMap.putAll(map);
466                         } else // multiples subnet_%aaid% outputs allowed
467                         {
468                             String subnetUUId = (String) outputs.get(key);
469                             sMap.put(key.substring("subnet_id_".length()), subnetUUId);
470                         }
471                     }
472                 }
473                 networkRollback.setNeutronNetworkId((String) outputs.get(NETWORK_ID));
474             }
475             subnetIdMap.value = sMap;
476
477             rollback.value = networkRollback;
478             // Populate remaining rollback info and response parameters.
479             networkRollback.setNetworkStackId(heatStack.getCanonicalName());
480             networkRollback.setNetworkCreated(true);
481             networkRollback.setNetworkType(networkType);
482
483             try {
484                 heat.updateResourceStatus(msoRequest.getRequestId(), NETWORK_CREATED_STATUS_MESSAGE);
485             } catch (Exception e) {
486                 logger.warn("Exception while updating infra active request", e);
487             }
488
489             logger.debug("Network {} successfully created via HEAT", networkName);
490         }
491
492         return;
493     }
494
495     @Override
496     public void updateNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
497             String networkId, String networkName, String physicalNetworkName, List<Integer> vlans, String shared,
498             String external, List<Subnet> subnets, Map<String, String> networkParams, MsoRequest msoRequest,
499             Holder<Map<String, String>> subnetIdMap, Holder<NetworkRollback> rollback) throws NetworkException {
500         updateNetwork(cloudSiteId, tenantId, networkType, modelCustomizationUuid, networkId, networkName,
501                 physicalNetworkName, vlans, null, shared, external, subnets, null, null, msoRequest, subnetIdMap,
502                 rollback);
503
504     }
505
506     @Override
507     public void updateNetworkContrail(String cloudSiteId, String tenantId, String networkType,
508             String modelCustomizationUuid, String networkId, String networkName, List<RouteTarget> routeTargets,
509             String shared, String external, List<Subnet> subnets, Map<String, String> networkParams,
510             List<String> policyFqdns, List<String> routeTableFqdns, MsoRequest msoRequest,
511             Holder<Map<String, String>> subnetIdMap, Holder<NetworkRollback> rollback) throws NetworkException {
512         updateNetwork(cloudSiteId, tenantId, networkType, modelCustomizationUuid, networkId, networkName, null, null,
513                 routeTargets, shared, external, subnets, policyFqdns, routeTableFqdns, msoRequest, subnetIdMap,
514                 rollback);
515     }
516
517     /**
518      * This is the "Update Network" web service implementation. It will update an existing Network of the requested type
519      * in the specified cloud and tenant. The typical use will be to replace the VLANs with the supplied list (to add or
520      * remove a VLAN), but other properties may be updated as well.
521      *
522      * There will be a pre-defined set of network types defined in the MSO Catalog. All such networks will have a
523      * similar configuration, based on the allowable Openstack networking definitions. This includes basic networks,
524      * provider networks (with a single VLAN), and multi-provider networks (one or more VLANs).
525      *
526      * Initially, all provider networks must currently be "vlan" type, and multi-provider networks must be multiple
527      * VLANs on the same physical network.
528      *
529      * This service supports two modes of Network update: - via Heat Templates - via Neutron API The network
530      * orchestration mode for each network type is declared in its catalog definition. All Heat-based templates must
531      * support some subset of the same input parameters: network_name, physical_network, vlan, segments.
532      *
533      * The method returns a NetworkRollback object. This object can be passed as-is to the rollbackNetwork operation to
534      * undo everything that was updated. This is useful if a network is successfully updated but orchestration fails on
535      * a subsequent operation.
536      */
537     private void updateNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
538             String networkId, String networkName, String physicalNetworkName, List<Integer> vlans,
539             List<RouteTarget> routeTargets, String shared, String external, List<Subnet> subnets,
540             List<String> policyFqdns, List<String> routeTableFqdns, MsoRequest msoRequest,
541             Holder<Map<String, String>> subnetIdMap, Holder<NetworkRollback> rollback) throws NetworkException {
542
543         logger.debug("***UPDATE Network adapter with Network: {} of type {} in {}/{}", networkName, networkType,
544                 cloudSiteId, tenantId);
545
546         // Will capture execution time for metrics
547         long startTime = System.currentTimeMillis();
548
549         // Build a default rollback object (no actions performed)
550         NetworkRollback networkRollback = new NetworkRollback();
551         networkRollback.setCloudId(cloudSiteId);
552         networkRollback.setTenantId(tenantId);
553         networkRollback.setMsoRequest(msoRequest);
554
555         Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
556         if (!cloudSiteOpt.isPresent()) {
557             String error = String.format(
558                     "UpdateNetwork: Configuration Error. Stack %s in %s/%s: CloudSite does not exist in MSO Configuration",
559                     networkName, cloudSiteId, tenantId);
560             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
561             // Set the detailed error as the Exception 'message'
562             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
563         }
564
565
566
567         NetworkResource networkResource = networkCheck(startTime, networkType, modelCustomizationUuid, networkName,
568                 physicalNetworkName, vlans, routeTargets, cloudSiteId, cloudSiteOpt.get());
569         String mode = networkResource.getOrchestrationMode();
570         NetworkType neutronNetworkType = NetworkType.valueOf(networkResource.getNeutronNetworkType());
571
572         // Use an MsoNeutronUtils for all Neutron commands
573
574         if (NEUTRON_MODE.equals(mode)) {
575
576             // Verify that the Network exists
577             // For Neutron-based orchestration, the networkId is the Neutron Network UUID.
578             NetworkInfo netInfo = null;
579             try {
580                 netInfo = neutron.queryNetwork(networkId, tenantId, cloudSiteId);
581             } catch (MsoException me) {
582                 me.addContext(UPDATE_NETWORK_CONTEXT);
583                 logger.error("{} {} Exception - queryNetwork query {} in {}/{} ", MessageEnum.RA_QUERY_NETWORK_EXC,
584                         ErrorCode.BusinessProcessError.getValue(), networkId, cloudSiteId, tenantId, me);
585                 throw new NetworkException(me);
586             }
587
588             if (netInfo == null) {
589                 String error = String.format("Update Nework: Network %s does not exist in %s/%s", networkId,
590                         cloudSiteId, tenantId);
591                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_NOT_FOUND,
592                         ErrorCode.BusinessProcessError.getValue(), error);
593                 // Does not exist. Throw an exception (can't update a non-existent network)
594                 throw new NetworkException(error, MsoExceptionCategory.USERDATA);
595             }
596             long updateNetworkStarttime = System.currentTimeMillis();
597             try {
598                 netInfo = neutron.updateNetwork(cloudSiteId, tenantId, networkId, neutronNetworkType,
599                         physicalNetworkName, vlans);
600             } catch (MsoException me) {
601                 me.addContext(UPDATE_NETWORK_CONTEXT);
602                 logger.error("{} {} Exception - updateNetwork {} in {}/{} ", MessageEnum.RA_UPDATE_NETWORK_ERR,
603                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
604                 throw new NetworkException(me);
605             }
606
607             // Add the network ID and previously queried vlans to the rollback object
608             networkRollback.setNetworkId(netInfo.getId());
609             networkRollback.setNeutronNetworkId(netInfo.getId());
610             networkRollback.setNetworkType(networkType);
611             // Save previous parameters
612             networkRollback.setNetworkName(netInfo.getName());
613             networkRollback.setPhysicalNetwork(netInfo.getProvider());
614             networkRollback.setVlans(netInfo.getVlans());
615
616             logger.debug("Network {} updated, id = {}", networkId, netInfo.getId());
617         } else if ("HEAT".equals(mode)) {
618
619             // First, look up to see that the Network already exists.
620             // For Heat-based orchestration, the networkId is the network Stack ID.
621             StackInfo heatStack = null;
622             try {
623                 heatStack = heat.queryStack(cloudSiteId, CLOUD_OWNER, tenantId, networkName);
624             } catch (MsoException me) {
625                 me.addContext(UPDATE_NETWORK_CONTEXT);
626                 logger.error("{} {} Exception - QueryStack query {} in {}/{} ", MessageEnum.RA_QUERY_NETWORK_EXC,
627                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
628                 throw new NetworkException(me);
629             }
630
631             if (heatStack == null || (heatStack.getStatus() == HeatStatus.NOTFOUND)) {
632                 String error = String.format("UpdateNetwork: Stack %s does not exist in %s/%s", networkName,
633                         cloudSiteId, tenantId);
634                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_NOT_FOUND, ErrorCode.DataError.getValue(),
635                         error);
636                 // Network stack does not exist. Return an error
637                 throw new NetworkException(error, MsoExceptionCategory.USERDATA);
638             }
639
640             // Get the previous parameters for rollback
641             Map<String, Object> heatParams = heatStack.getParameters();
642
643             String previousNetworkName = (String) heatParams.get("network_name");
644             String previousPhysicalNetwork = (String) heatParams.get(PHYSICAL_NETWORK);
645
646             List<Integer> previousVlans = new ArrayList<>();
647             String vlansParam = (String) heatParams.get(VLANS);
648             if (vlansParam != null) {
649                 for (String vlan : vlansParam.split(",")) {
650                     try {
651                         previousVlans.add(Integer.parseInt(vlan));
652                     } catch (NumberFormatException e) {
653                         logger.warn("{} {} Exception - VLAN parse for params {} ", MessageEnum.RA_VLAN_PARSE,
654                                 ErrorCode.DataError.getValue(), vlansParam, e);
655                     }
656                 }
657             }
658             logger.debug("Update Stack:  Previous VLANS: {}", previousVlans);
659
660             // Ready to deploy the updated Network via Heat
661
662
663             HeatTemplate heatTemplate = networkResource.getHeatTemplate();
664             if (heatTemplate == null) {
665                 String error = "Network error - undefined Heat Template. Network Type=" + networkType;
666                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_PARAM_NOT_FOUND, ErrorCode.DataError.getValue(),
667                         error);
668                 throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
669             }
670
671             logger.debug("Got HEAT Template from DB: {}", heatTemplate);
672
673             // "Fix" the template if it has CR/LF (getting this from Oracle)
674             String template = heatTemplate.getHeatTemplate();
675             template = template.replaceAll("\r\n", "\n");
676
677             boolean aic3template = false;
678             String aic3nw = AIC3_NW;
679
680             aic3nw = environment.getProperty(AIC3_NW_PROPERTY, AIC3_NW);
681
682             if (template.contains(aic3nw))
683                 aic3template = true;
684
685             // Build the common set of HEAT template parameters
686             Map<String, Object> stackParams = populateNetworkParams(neutronNetworkType, networkName,
687                     physicalNetworkName, vlans, routeTargets, shared, external, aic3template);
688
689             // Validate (and update) the input parameters against the DB definition
690             // Shouldn't happen unless DB config is wrong, since all networks use same inputs
691             try {
692                 stackParams = heat.validateStackParams(stackParams, heatTemplate);
693             } catch (IllegalArgumentException e) {
694                 String error = "UpdateNetwork: Configuration Error: Network Type=" + networkType;
695                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
696                 throw new NetworkException(error, MsoExceptionCategory.INTERNAL, e);
697             }
698
699             if (subnets != null) {
700                 try {
701                     if (aic3template) {
702                         template = mergeSubnetsAIC3(template, subnets, stackParams);
703                     } else {
704                         template = mergeSubnets(template, subnets);
705                     }
706                 } catch (MsoException me) {
707                     me.addContext(UPDATE_NETWORK_CONTEXT);
708                     logger.error("{} {} Exception - UpdateNetwork mergeSubnets for network type {} in {}/{} ",
709                             MessageEnum.RA_UPDATE_NETWORK_ERR, ErrorCode.DataError.getValue(),
710                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
711                     throw new NetworkException(me);
712                 }
713             }
714
715             if (policyFqdns != null && aic3template) {
716                 try {
717                     mergePolicyRefs(policyFqdns, stackParams);
718                 } catch (MsoException me) {
719                     me.addContext(UPDATE_NETWORK_CONTEXT);
720                     logger.error("{} {} Exception - UpdateNetwork mergePolicyRefs type {} in {}/{} ",
721                             MessageEnum.RA_UPDATE_NETWORK_ERR, ErrorCode.DataError.getValue(),
722                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
723                     throw new NetworkException(me);
724                 }
725             }
726
727             if (routeTableFqdns != null && !routeTableFqdns.isEmpty() && aic3template) {
728                 try {
729                     mergeRouteTableRefs(routeTableFqdns, stackParams);
730                 } catch (MsoException me) {
731                     me.addContext(UPDATE_NETWORK_CONTEXT);
732                     logger.error("{} {} Exception - UpdateNetwork mergeRouteTableRefs type {} in {}/{} ",
733                             MessageEnum.RA_UPDATE_NETWORK_ERR, ErrorCode.DataError.getValue(),
734                             neutronNetworkType.toString(), cloudSiteId, tenantId, me);
735                     throw new NetworkException(me);
736                 }
737             }
738
739             // Update the network stack
740             // Ignore MsoStackNotFound exception because we already checked.
741             try {
742                 heatStack = heatWithUpdate.updateStack(cloudSiteId, CLOUD_OWNER, tenantId, networkId, template,
743                         stackParams, true, heatTemplate.getTimeoutMinutes());
744             } catch (MsoException me) {
745                 me.addContext(UPDATE_NETWORK_CONTEXT);
746                 logger.error("{} {} Exception - update network {} in {}/{} ", MessageEnum.RA_UPDATE_NETWORK_ERR,
747                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
748                 throw new NetworkException(me);
749             }
750
751             Map<String, Object> outputs = heatStack.getOutputs();
752             Map<String, String> sMap = new HashMap<>();
753             if (outputs != null) {
754                 for (Map.Entry<String, Object> entry : outputs.entrySet()) {
755                     String key = entry.getKey();
756                     if (key != null && key.startsWith("subnet")) {
757                         if (aic3template) // one subnet output expected
758                         {
759                             Map<String, String> map = getSubnetUUId(key, outputs, subnets);
760                             sMap.putAll(map);
761                         } else // multiples subnet_%aaid% outputs allowed
762                         {
763                             String subnetUUId = (String) outputs.get(key);
764                             sMap.put(key.substring("subnet_id_".length()), subnetUUId);
765                         }
766                     }
767                 }
768             }
769             subnetIdMap.value = sMap;
770
771             // Reach this point if createStack is successful.
772             // Populate remaining rollback info and response parameters.
773             networkRollback.setNetworkStackId(heatStack.getCanonicalName());
774             if (null != outputs) {
775                 networkRollback.setNeutronNetworkId((String) outputs.get(NETWORK_ID));
776             } else {
777                 logger.debug("outputs is NULL");
778             }
779             networkRollback.setNetworkType(networkType);
780             // Save previous parameters
781             networkRollback.setNetworkName(previousNetworkName);
782             networkRollback.setPhysicalNetwork(previousPhysicalNetwork);
783             networkRollback.setVlans(previousVlans);
784
785             rollback.value = networkRollback;
786
787             logger.debug("Network {} successfully updated via HEAT", networkId);
788         }
789
790         return;
791     }
792
793     private NetworkResource networkCheck(long startTime, String networkType, String modelCustomizationUuid,
794             String networkName, String physicalNetworkName, List<Integer> vlans, List<RouteTarget> routeTargets,
795             String cloudSiteId, CloudSite cloudSite) throws NetworkException {
796         // Retrieve the Network Resource definition
797         NetworkResource networkResource = null;
798         NetworkResourceCustomization networkCust = null;
799         CollectionNetworkResourceCustomization collectionNetworkCust = null;
800         if (commonUtils.isNullOrEmpty(modelCustomizationUuid)) {
801             if (!commonUtils.isNullOrEmpty(networkType)) {
802                 networkResource = networkResourceRepo.findFirstByModelNameOrderByModelVersionDesc(networkType);
803             }
804         } else {
805             networkCust = networkCustomRepo.findOneByModelCustomizationUUID(modelCustomizationUuid);
806             if (networkCust == null) {
807                 collectionNetworkCust =
808                         collectionNetworkCustomRepo.findOneByModelCustomizationUUID(modelCustomizationUuid);
809             }
810         }
811         if (networkCust != null) {
812             logger.debug("Got Network Customization definition from Catalog: {}", networkCust);
813
814             networkResource = networkCust.getNetworkResource();
815         } else if (collectionNetworkCust != null) {
816             logger.debug("Retrieved Collection Network Resource Customization from Catalog: {}", collectionNetworkCust);
817             networkResource = collectionNetworkCust.getNetworkResource();
818         }
819         if (networkResource == null) {
820             String error = String.format(
821                     "Create/UpdateNetwork: Unable to get network resource with NetworkType: %s or ModelCustomizationUUID:%s",
822                     networkType, modelCustomizationUuid);
823             logger.error(LoggingAnchor.THREE, MessageEnum.RA_UNKOWN_PARAM, ErrorCode.DataError.getValue(), error);
824
825             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
826         }
827         logger.debug(LOG_DEBUG_MSG, networkResource);
828
829         String mode = networkResource.getOrchestrationMode();
830         NetworkType neutronNetworkType = NetworkType.valueOf(networkResource.getNeutronNetworkType());
831
832         // All Networks are orchestrated via HEAT or Neutron
833         if (!("HEAT".equals(mode) || NEUTRON_MODE.equals(mode))) {
834             String error = "CreateNetwork: Configuration Error: Network Type = " + networkType;
835             logger.error(LoggingAnchor.THREE, MessageEnum.RA_NETWORK_ORCHE_MODE_NOT_SUPPORT,
836                     ErrorCode.DataError.getValue(), error);
837             throw new NetworkException(error, MsoExceptionCategory.INTERNAL);
838         }
839
840         MavenLikeVersioning aicV = new MavenLikeVersioning();
841         aicV.setVersion(cloudSite.getCloudVersion());
842         if ((aicV.isMoreRecentThan(networkResource.getAicVersionMin())
843                 || aicV.isTheSameVersion(networkResource.getAicVersionMin())) // aic
844                 // >=
845                 // min
846                 && (aicV.isTheSameVersion(networkResource.getAicVersionMax())
847                         || !(aicV.isMoreRecentThan(networkResource.getAicVersionMax())))) // aic <= max
848         {
849             logger.debug("Network Type:{} VersionMin:{} VersionMax:{} supported on Cloud:{} with AIC_Version:{}",
850                     networkType, networkResource.getAicVersionMin(), networkResource.getAicVersionMax(), cloudSiteId,
851                     cloudSite.getCloudVersion());
852         } else {
853             String error = String.format(
854                     "Network Type:%s Version_Min:%s Version_Max:%s not supported on Cloud:%s with AIC_Version:%s",
855                     networkType, networkResource.getAicVersionMin(), networkResource.getAicVersionMax(), cloudSiteId,
856                     cloudSite.getCloudVersion());
857             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
858             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
859         }
860
861         // Validate the Network parameters.
862         String missing =
863                 validateNetworkParams(neutronNetworkType, networkName, physicalNetworkName, vlans, routeTargets);
864         if (!missing.isEmpty()) {
865             String error = "Create Network: Missing parameters: " + missing;
866             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
867
868             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
869         }
870
871         return networkResource;
872     }
873
874     @Override
875     public void queryNetwork(String cloudSiteId, String tenantId, String networkNameOrId, MsoRequest msoRequest,
876             Holder<Boolean> networkExists, Holder<String> networkId, Holder<String> neutronNetworkId,
877             Holder<NetworkStatus> status, Holder<List<Integer>> vlans, Holder<Map<String, String>> subnetIdMap)
878             throws NetworkException {
879         queryNetwork(cloudSiteId, tenantId, networkNameOrId, msoRequest, networkExists, networkId, neutronNetworkId,
880                 status, vlans, null, subnetIdMap);
881     }
882
883     @Override
884     public void queryNetworkContrail(String cloudSiteId, String tenantId, String networkNameOrId, MsoRequest msoRequest,
885             Holder<Boolean> networkExists, Holder<String> networkId, Holder<String> neutronNetworkId,
886             Holder<NetworkStatus> status, Holder<List<RouteTarget>> routeTargets,
887             Holder<Map<String, String>> subnetIdMap) throws NetworkException {
888         queryNetwork(cloudSiteId, tenantId, networkNameOrId, msoRequest, networkExists, networkId, neutronNetworkId,
889                 status, null, routeTargets, subnetIdMap);
890     }
891
892     /**
893      * This is the queryNetwork method. It returns the existence and status of the specified network, along with its
894      * Neutron UUID and list of VLANs. This method attempts to find the network using both Heat and Neutron. Heat stacks
895      * are first searched based on the provided network name/id. If none is found, the Neutron is directly queried.
896      */
897     private void queryNetwork(String cloudSiteId, String tenantId, String networkNameOrId, MsoRequest msoRequest,
898             Holder<Boolean> networkExists, Holder<String> networkId, Holder<String> neutronNetworkId,
899             Holder<NetworkStatus> status, Holder<List<Integer>> vlans, Holder<List<RouteTarget>> routeTargets,
900             Holder<Map<String, String>> subnetIdMap) throws NetworkException {
901
902         logger.debug("*** QUERY Network with Network: {} in {}/{}", networkNameOrId, cloudSiteId, tenantId);
903
904         if (commonUtils.isNullOrEmpty(cloudSiteId) || commonUtils.isNullOrEmpty(tenantId)
905                 || commonUtils.isNullOrEmpty(networkNameOrId)) {
906
907             String error = "Missing mandatory parameter cloudSiteId, tenantId or networkId";
908             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
909             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
910         }
911
912         Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
913         if (!cloudSiteOpt.isPresent()) {
914             String error = String.format(
915                     "Configuration Error. Stack %s in %s/%s: CloudSite does not exist in MSO Configuration",
916                     networkNameOrId, cloudSiteId, tenantId);
917             logger.error(LoggingAnchor.THREE, MessageEnum.RA_CONFIG_EXC, ErrorCode.DataError.getValue(), error);
918             // Set the detailed error as the Exception 'message'
919             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
920         }
921
922         // Use MsoNeutronUtils for all NEUTRON commands
923
924         String mode;
925         String neutronId = null;
926         // Try Heat first, since networks may be named the same as the Heat stack
927         StackInfo heatStack = null;
928         try {
929             heatStack = heat.queryStack(cloudSiteId, CLOUD_OWNER, tenantId, networkNameOrId);
930         } catch (MsoException me) {
931             me.addContext("QueryNetwork");
932             logger.error("{} {} Exception - Query Network (heat): {} in {}/{} ", MessageEnum.RA_QUERY_NETWORK_EXC,
933                     ErrorCode.DataError.getValue(), networkNameOrId, cloudSiteId, tenantId, me);
934             throw new NetworkException(me);
935         }
936
937         // Populate the outputs based on the returned Stack information
938         if (heatStack != null && heatStack.getStatus() != HeatStatus.NOTFOUND) {
939             // Found it. Get the neutronNetworkId for further query
940             Map<String, String> sMap = new HashMap<>();
941             Map<String, Object> outputs = heatStack.getOutputs();
942             mode = "HEAT";
943             if (outputs != null) {
944                 neutronId = (String) outputs.get(NETWORK_ID);
945
946                 for (String key : outputs.keySet()) {
947                     if (key != null && key.startsWith("subnet_id_")) // multiples subnet_%aaid% outputs
948                     {
949                         String subnetUUId = (String) outputs.get(key);
950                         sMap.put(key.substring("subnet_id_".length()), subnetUUId);
951                     } else if (key != null && key.startsWith("subnet")) // one subnet output expected
952                     {
953                         Map<String, String> map = getSubnetUUId(key, outputs, null);
954                         sMap.putAll(map);
955                     }
956
957                 }
958             }
959             subnetIdMap.value = sMap;
960         } else {
961             // Input ID was not a Heat stack ID. Try it directly in Neutron
962             neutronId = networkNameOrId;
963             mode = NEUTRON_MODE;
964         }
965
966         // Query directly against the Neutron Network for the details
967         // no RouteTargets available for ContrailV2 in neutron net-show
968         // networkId is heatStackId
969         try {
970             NetworkInfo netInfo = neutron.queryNetwork(neutronId, tenantId, cloudSiteId);
971             if (netInfo != null) {
972                 // Found. Populate the output elements
973                 networkExists.value = Boolean.TRUE;
974                 if ("HEAT".equals(mode) && heatStack != null) {
975                     networkId.value = heatStack.getCanonicalName();
976                 } else {
977                     networkId.value = netInfo.getId();
978                 }
979                 neutronNetworkId.value = netInfo.getId();
980                 status.value = netInfo.getStatus();
981                 if (vlans != null)
982                     vlans.value = netInfo.getVlans();
983
984                 logger.debug("Network {} found({}), ID = {}{}", networkNameOrId, mode, networkId.value,
985                         ("HEAT".equals(mode) ? ",NeutronId = " + neutronNetworkId.value : ""));
986             } else {
987                 // Not found. Populate the status fields, leave the rest null
988                 networkExists.value = Boolean.FALSE;
989                 status.value = NetworkStatus.NOTFOUND;
990                 neutronNetworkId.value = null;
991                 if (vlans != null)
992                     vlans.value = new ArrayList<>();
993
994                 logger.debug("Network {} not found", networkNameOrId);
995             }
996         } catch (MsoException me) {
997             me.addContext("QueryNetwork");
998             logger.error("{} {} Exception - Query Network (neutron): {} in {}/{} ", MessageEnum.RA_QUERY_NETWORK_EXC,
999                     ErrorCode.DataError.getValue(), networkNameOrId, cloudSiteId, tenantId, me);
1000             throw new NetworkException(me);
1001         }
1002         return;
1003     }
1004
1005     /**
1006      * This is the "Delete Network" web service implementation. It will delete a Network in the specified cloud and
1007      * tenant.
1008      *
1009      * If the network is not found, it is treated as a success.
1010      *
1011      * This service supports two modes of Network creation/update/delete: - via Heat Templates - via Neutron API The
1012      * network orchestration mode for each network type is declared in its catalog definition.
1013      *
1014      * For Heat-based orchestration, the networkId should be the stack ID. For Neutron-based orchestration, the
1015      * networkId should be the Neutron network UUID.
1016      *
1017      * The method returns nothing on success. Rollback is not possible for delete commands, so any failure on delete
1018      * will require manual fallout in the client.
1019      */
1020     @Override
1021     public void deleteNetwork(String cloudSiteId, String tenantId, String networkType, String modelCustomizationUuid,
1022             String networkId, MsoRequest msoRequest, Holder<Boolean> networkDeleted) throws NetworkException {
1023         logger.debug("*** DELETE Network adapter with Network: {} in {}/{}", networkId, cloudSiteId, tenantId);
1024         if (commonUtils.isNullOrEmpty(cloudSiteId) || commonUtils.isNullOrEmpty(tenantId)
1025                 || commonUtils.isNullOrEmpty(networkId)) {
1026             String error = "Missing mandatory parameter cloudSiteId, tenantId or networkId";
1027             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
1028             throw new NetworkException(error, MsoExceptionCategory.USERDATA);
1029         }
1030
1031         // Retrieve the Network Resource definition
1032         NetworkResource networkResource = null;
1033         if (commonUtils.isNullOrEmpty(modelCustomizationUuid)) {
1034             if (!commonUtils.isNullOrEmpty(networkType)) {
1035                 networkResource = networkResourceRepo.findFirstByModelNameOrderByModelVersionDesc(networkType);
1036             }
1037         } else {
1038             NetworkResourceCustomization nrc =
1039                     networkCustomRepo.findOneByModelCustomizationUUID(modelCustomizationUuid);
1040             if (nrc != null) {
1041                 networkResource = nrc.getNetworkResource();
1042             }
1043         }
1044
1045         int timeoutMinutes = 118;
1046         String mode = "";
1047         if (networkResource != null) {
1048             logger.debug(LOG_DEBUG_MSG, networkResource.toString());
1049             mode = networkResource.getOrchestrationMode();
1050             networkResource.getHeatTemplate().getTimeoutMinutes();
1051             HeatTemplate heat = networkResource.getHeatTemplate();
1052             if (heat != null && heat.getTimeoutMinutes() != null) {
1053                 if (heat.getTimeoutMinutes() < 118) {
1054                     timeoutMinutes = heat.getTimeoutMinutes();
1055                 }
1056             }
1057         }
1058
1059         if (NEUTRON_MODE.equals(mode)) {
1060             try {
1061                 boolean deleted = neutron.deleteNetwork(networkId, tenantId, cloudSiteId);
1062                 networkDeleted.value = deleted;
1063             } catch (MsoException me) {
1064                 me.addContext("DeleteNetwork");
1065                 logger.error("{} {} Delete Network (neutron): {} in {}/{} ", MessageEnum.RA_DELETE_NETWORK_EXC,
1066                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
1067                 throw new NetworkException(me);
1068             }
1069         } else {
1070             try {
1071                 StackInfo stack = heat.deleteStack(tenantId, CLOUD_OWNER, cloudSiteId, networkId, true, timeoutMinutes);
1072                 networkDeleted.value = stack.isOperationPerformed();
1073             } catch (MsoException me) {
1074                 me.addContext("DeleteNetwork");
1075                 logger.error("{} {} Delete Network (heat): {} in {}/{} ", MessageEnum.RA_DELETE_NETWORK_EXC,
1076                         ErrorCode.DataError.getValue(), networkId, cloudSiteId, tenantId, me);
1077                 throw new NetworkException(me);
1078             }
1079         }
1080         try {
1081             heat.updateResourceStatus(msoRequest.getRequestId(),
1082                     networkDeleted.value ? NETWORK_DELETED_STATUS_MESSAGE : NETWORK_NOT_EXIST_STATUS_MESSAGE);
1083         } catch (Exception e) {
1084             logger.warn("Exception while updating infra active request", e);
1085         }
1086     }
1087
1088     /**
1089      * This web service endpoint will rollback a previous Create VNF operation. A rollback object is returned to the
1090      * client in a successful creation response. The client can pass that object as-is back to the rollbackVnf operation
1091      * to undo the creation.
1092      *
1093      * The rollback includes removing the VNF and deleting the tenant if the tenant did not exist prior to the VNF
1094      * creation.
1095      */
1096     @Override
1097     public void rollbackNetwork(NetworkRollback rollback) throws NetworkException {
1098         if (rollback == null) {
1099             logger.error("{} {} rollback is null", MessageEnum.RA_ROLLBACK_NULL, ErrorCode.DataError.getValue());
1100             return;
1101         }
1102
1103         // Get the elements of the VnfRollback object for easier access
1104         String cloudSiteId = rollback.getCloudId();
1105         String tenantId = rollback.getTenantId();
1106         String networkId = rollback.getNetworkStackId();
1107         String networkType = rollback.getNetworkType();
1108         String modelCustomizationUuid = rollback.getModelCustomizationUuid();
1109
1110         logger.debug("*** ROLLBACK Network {} in {}/{}", networkId, cloudSiteId, tenantId);
1111         // Retrieve the Network Resource definition
1112         NetworkResource networkResource = null;
1113         if (commonUtils.isNullOrEmpty(modelCustomizationUuid)) {
1114             networkResource = networkCustomRepo.findOneByNetworkType(networkType).getNetworkResource();
1115         } else {
1116             networkResource =
1117                     networkCustomRepo.findOneByModelCustomizationUUID(modelCustomizationUuid).getNetworkResource();
1118         }
1119         String mode = "";
1120         if (networkResource != null) {
1121
1122             logger.debug(LOG_DEBUG_MSG, networkResource);
1123
1124             mode = networkResource.getOrchestrationMode();
1125         }
1126
1127         if (rollback.getNetworkCreated()) {
1128             if (NEUTRON_MODE.equals(mode)) {
1129                 try {
1130                     neutron.deleteNetwork(networkId, tenantId, cloudSiteId);
1131                 } catch (MsoException me) {
1132                     me.addContext("RollbackNetwork");
1133                     logger.error("{} {} Exception - Rollback Network (neutron): {} in {}/{} ",
1134                             MessageEnum.RA_DELETE_NETWORK_EXC, ErrorCode.BusinessProcessError.getValue(), networkId,
1135                             cloudSiteId, tenantId, me);
1136                     throw new NetworkException(me);
1137                 }
1138             } else {
1139                 try {
1140                     heat.deleteStack(tenantId, CLOUD_OWNER, cloudSiteId, networkId, true, 120);
1141                 } catch (MsoException me) {
1142                     me.addContext("RollbackNetwork");
1143                     logger.error("{} {} Exception - Rollback Network (heat): {} in {}/{} ",
1144                             MessageEnum.RA_DELETE_NETWORK_EXC, ErrorCode.BusinessProcessError.getValue(), networkId,
1145                             cloudSiteId, tenantId, me);
1146                     throw new NetworkException(me);
1147                 }
1148             }
1149         }
1150     }
1151
1152     private String validateNetworkParams(NetworkType neutronNetworkType, String networkName, String physicalNetwork,
1153             List<Integer> vlans, List<RouteTarget> routeTargets) {
1154         String sep = "";
1155         StringBuilder missing = new StringBuilder();
1156         if (commonUtils.isNullOrEmpty(networkName)) {
1157             missing.append("networkName");
1158             sep = ",";
1159         }
1160
1161         if (neutronNetworkType == NetworkType.PROVIDER || neutronNetworkType == NetworkType.MULTI_PROVIDER) {
1162             if (commonUtils.isNullOrEmpty(physicalNetwork)) {
1163                 missing.append(sep).append("physicalNetworkName");
1164                 sep = ",";
1165             }
1166             if (vlans == null || vlans.isEmpty()) {
1167                 missing.append(sep).append(VLANS);
1168             }
1169         }
1170
1171         return missing.toString();
1172     }
1173
1174     private Map<String, Object> populateNetworkParams(NetworkType neutronNetworkType, String networkName,
1175             String physicalNetwork, List<Integer> vlans, List<RouteTarget> routeTargets, String shared, String external,
1176             boolean aic3template) {
1177         // Build the common set of HEAT template parameters
1178         Map<String, Object> stackParams = new HashMap<>();
1179         stackParams.put("network_name", networkName);
1180
1181         if (neutronNetworkType == NetworkType.PROVIDER) {
1182             // For Provider type
1183             stackParams.put(PHYSICAL_NETWORK, physicalNetwork);
1184             stackParams.put("vlan", vlans.get(0).toString());
1185         } else if (neutronNetworkType == NetworkType.MULTI_PROVIDER) {
1186             // For Multi-provider, PO supports a custom resource extension of ProviderNet.
1187             // It supports all ProviderNet properties except segmentation_id, and adds a
1188             // comma-separated-list of VLANs as a "segments" property.
1189             // Note that this does not match the Neutron definition of Multi-Provider network,
1190             // which contains a list of 'segments', each having physical_network, network_type,
1191             // and segmentation_id.
1192             StringBuilder buf = new StringBuilder();
1193             String sep = "";
1194             for (Integer vlan : vlans) {
1195                 buf.append(sep).append(vlan.toString());
1196                 sep = ",";
1197             }
1198             String csl = buf.toString();
1199
1200             stackParams.put(PHYSICAL_NETWORK, physicalNetwork);
1201             stackParams.put(VLANS, csl);
1202         }
1203         if (routeTargets != null) {
1204
1205             String rtGlobal = "";
1206             String rtImport = "";
1207             String rtExport = "";
1208             String sep = "";
1209             for (RouteTarget rt : routeTargets) {
1210                 boolean rtIsNull = false;
1211                 if (rt != null) {
1212                     String routeTarget = rt.getRouteTarget();
1213                     String routeTargetRole = rt.getRouteTargetRole();
1214                     logger.debug("Checking for an actually null route target: {}", rt);
1215                     if (routeTarget == null || routeTarget.equals("") || routeTarget.equalsIgnoreCase("null"))
1216                         rtIsNull = true;
1217                     if (routeTargetRole == null || routeTargetRole.equals("")
1218                             || routeTargetRole.equalsIgnoreCase("null"))
1219                         rtIsNull = true;
1220                 } else {
1221                     rtIsNull = true;
1222                 }
1223                 if (!rtIsNull) {
1224                     logger.debug("Input RT:{}", rt);
1225                     String role = rt.getRouteTargetRole();
1226                     String rtValue = rt.getRouteTarget();
1227
1228                     if ("IMPORT".equalsIgnoreCase(role)) {
1229                         sep = rtImport.isEmpty() ? "" : ",";
1230                         rtImport = aic3template ? rtImport + sep + "target:" + rtValue : rtImport + sep + rtValue;
1231                     } else if ("EXPORT".equalsIgnoreCase(role)) {
1232                         sep = rtExport.isEmpty() ? "" : ",";
1233                         rtExport = aic3template ? rtExport + sep + "target:" + rtValue : rtExport + sep + rtValue;
1234                     } else // covers BOTH, empty etc
1235                     {
1236                         sep = rtGlobal.isEmpty() ? "" : ",";
1237                         rtGlobal = aic3template ? rtGlobal + sep + "target:" + rtValue : rtGlobal + sep + rtValue;
1238                     }
1239
1240                 }
1241             }
1242
1243             if (!rtImport.isEmpty()) {
1244                 stackParams.put("route_targets_import", rtImport);
1245             }
1246             if (!rtExport.isEmpty()) {
1247                 stackParams.put("route_targets_export", rtExport);
1248             }
1249             if (!rtGlobal.isEmpty()) {
1250                 stackParams.put("route_targets", rtGlobal);
1251             }
1252         }
1253         if (commonUtils.isNullOrEmpty(shared)) {
1254             stackParams.put("shared", "False");
1255         } else {
1256             stackParams.put("shared", shared);
1257         }
1258         if (commonUtils.isNullOrEmpty(external)) {
1259             stackParams.put("external", "False");
1260         } else {
1261             stackParams.put("external", external);
1262         }
1263         return stackParams;
1264     }
1265
1266
1267
1268     /**
1269      * policyRef_list structure in stackParams [ { "network_policy_refs_data_sequence": {
1270      * "network_policy_refs_data_sequence_major": "1", "network_policy_refs_data_sequence_minor": "0" } }, {
1271      * "network_policy_refs_data_sequence": { "network_policy_refs_data_sequence_major": "2",
1272      * "network_policy_refs_data_sequence_minor": "0" } } ]
1273      **/
1274     private void mergePolicyRefs(List<String> pFqdns, Map<String, Object> stackParams) throws MsoException {
1275         // Resource Property
1276         List<ContrailPolicyRef> prlist = new ArrayList<>();
1277         int index = 1;
1278
1279         if (pFqdns != null) {
1280             for (String pf : pFqdns) {
1281                 if (!commonUtils.isNullOrEmpty(pf)) {
1282                     ContrailPolicyRef pr = new ContrailPolicyRef();
1283                     ContrailPolicyRefSeq refSeq = new ContrailPolicyRefSeq(String.valueOf(index), "0");
1284                     pr.setSeq(refSeq);
1285                     index++;
1286                     logger.debug("Contrail PolicyRefs Data:{}", pr);
1287                     prlist.add(pr);
1288                 }
1289             }
1290         } else {
1291             String error = "Null pFqdns at start of mergePolicyRefs";
1292             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MARSHING_ERROR, ErrorCode.BusinessProcessError.getValue(),
1293                     error);
1294             throw new MsoAdapterException(error);
1295         }
1296
1297         JsonNode node = null;
1298         try {
1299             ObjectMapper mapper = new ObjectMapper();
1300             node = mapper.convertValue(prlist, JsonNode.class);
1301             String jsonString = mapper.writeValueAsString(prlist);
1302             logger.debug("Json PolicyRefs Data:{}", jsonString);
1303         } catch (Exception e) {
1304             String error = "Error creating JsonNode for policyRefs Data";
1305             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MARSHING_ERROR, ErrorCode.BusinessProcessError.getValue(),
1306                     error, e);
1307             throw new MsoAdapterException(error);
1308         }
1309         // update parameters
1310         if (pFqdns != null && node != null) {
1311             StringBuilder buf = new StringBuilder();
1312             String sep = "";
1313             for (String pf : pFqdns) {
1314                 if (!commonUtils.isNullOrEmpty(pf)) {
1315                     buf.append(sep).append(pf);
1316                     sep = ",";
1317                 }
1318             }
1319             String csl = buf.toString();
1320             stackParams.put("policy_refs", csl);
1321             stackParams.put("policy_refsdata", node);
1322         }
1323
1324         logger.debug("StackParams updated with policy refs");
1325         return;
1326     }
1327
1328     private void mergeRouteTableRefs(List<String> rtFqdns, Map<String, Object> stackParams) throws MsoException {
1329
1330         // update parameters
1331         if (rtFqdns != null) {
1332             StringBuilder buf = new StringBuilder();
1333             String sep = "";
1334             for (String rtf : rtFqdns) {
1335                 if (!commonUtils.isNullOrEmpty(rtf)) {
1336                     buf.append(sep).append(rtf);
1337                     sep = ",";
1338                 }
1339             }
1340             String csl = buf.toString();
1341             stackParams.put("route_table_refs", csl);
1342         }
1343
1344         logger.debug("StackParams updated with route_table refs");
1345         return;
1346     }
1347
1348
1349     /***
1350      * Subnet Output structure from Juniper { "ipam_subnets": [ { "subnet": { "ip_prefix": "10.100.1.0",
1351      * "ip_prefix_len": 28 }, "addr_from_start": null, "enable_dhcp": false, "default_gateway": "10.100.1.1",
1352      * "dns_nameservers": [], "dhcp_option_list": null, "subnet_uuid": "10391fbf-6b9c-4160-825d-2d018b7649cf",
1353      * "allocation_pools": [ { "start": "10.100.1.3", "end": "10.100.1.5" }, { "start": "10.100.1.6", "end":
1354      * "10.100.1.9" } ], "host_routes": null, "dns_server_address": "10.100.1.13", "subnet_name":
1355      * "subnet_MsoNW1_692c9032-e1a2-4d64-828c-7b9a4fcc05b0" }, { "subnet": { "ip_prefix": "10.100.2.16",
1356      * "ip_prefix_len": 28 }, "addr_from_start": null, "enable_dhcp": true, "default_gateway": "10.100.2.17",
1357      * "dns_nameservers": [], "dhcp_option_list": null, "subnet_uuid": "c7aac5ea-66fe-443a-85f9-9c38a608c0f6",
1358      * "allocation_pools": [ { "start": "10.100.2.18", "end": "10.100.2.20" } ], "host_routes": null,
1359      * "dns_server_address": "10.100.2.29", "subnet_name": "subnet_MsoNW1_692c9032-e1a2-4d64-828c-7b9a4fcc05b1" } ],
1360      * "host_routes": null }
1361      ***/
1362     private String mergeSubnetsAIC3(String heatTemplate, List<Subnet> subnets, Map<String, Object> stackParams)
1363             throws MsoException {
1364
1365         // Resource Property
1366         List<ContrailSubnet> cslist = new ArrayList<>();
1367         for (Subnet subnet : subnets) {
1368             logger.debug("Input Subnet:{}", subnet);
1369             ContrailSubnet cs = new ContrailSubnetMapper(subnet).map();
1370             logger.debug("Contrail Subnet:{}", cs);
1371             cslist.add(cs);
1372         }
1373
1374         JsonNode node = null;
1375         try {
1376             ObjectMapper mapper = new ObjectMapper();
1377             node = mapper.convertValue(cslist, JsonNode.class);
1378             String jsonString = mapper.writeValueAsString(cslist);
1379             logger.debug("Json Subnet List:{}", jsonString);
1380         } catch (Exception e) {
1381             String error = "Error creating JsonNode from input subnets";
1382             logger.error(LoggingAnchor.THREE, MessageEnum.RA_MARSHING_ERROR, ErrorCode.DataError.getValue(), error, e);
1383             throw new MsoAdapterException(error);
1384         }
1385         // update parameters
1386         if (node != null) {
1387             stackParams.put("subnet_list", node);
1388         }
1389         // Outputs - All subnets are in one ipam_subnets structure
1390         String outputTempl = "  subnet:\n" + "    description: Openstack subnet identifier\n"
1391                 + "    value: { get_attr: [network, network_ipam_refs, 0, attr]}\n";
1392
1393         // append outputs in heatTemplate
1394         int outputsIdx = heatTemplate.indexOf("outputs:");
1395         heatTemplate = insertStr(heatTemplate, outputTempl, outputsIdx + 8);
1396         logger.debug("Template updated with all AIC3.0 subnets:{}", heatTemplate);
1397         return heatTemplate;
1398     }
1399
1400
1401     private String mergeSubnets(String heatTemplate, List<Subnet> subnets) throws MsoException {
1402
1403         String resourceTempl = "  subnet_%subnetId%:\n" + "    type: OS::Neutron::Subnet\n" + "    properties:\n"
1404                 + "      name: %name%\n" + "      network_id: { get_resource: network }\n" + "      cidr: %cidr%\n";
1405
1406         /*
1407          * make these optional + "      ip_version: %ipversion%\n" + "      enable_dhcp: %enabledhcp%\n" +
1408          * "      gateway_ip: %gatewayip%\n" + "      allocation_pools:\n" + "       - start: %poolstart%\n" +
1409          * "         end: %poolend%\n";
1410          *
1411          */
1412
1413         String outputTempl = "  subnet_id_%subnetId%:\n" + "    description: Openstack subnet identifier\n"
1414                 + "    value: {get_resource: subnet_%subnetId%}\n";
1415
1416         String curR;
1417         String curO;
1418         StringBuilder resourcesBuf = new StringBuilder();
1419         StringBuilder outputsBuf = new StringBuilder();
1420         for (Subnet subnet : subnets) {
1421
1422             // build template for each subnet
1423             curR = resourceTempl;
1424             if (subnet.getSubnetId() != null) {
1425                 curR = curR.replace("%subnetId%", subnet.getSubnetId());
1426             } else {
1427                 String error = "Missing Required AAI SubnetId for subnet in HEAT Template";
1428                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
1429                 throw new MsoAdapterException(error);
1430             }
1431
1432             if (subnet.getSubnetName() != null) {
1433                 curR = curR.replace("%name%", subnet.getSubnetName());
1434             } else {
1435                 curR = curR.replace("%name%", subnet.getSubnetId());
1436             }
1437
1438             if (subnet.getCidr() != null) {
1439                 curR = curR.replace("%cidr%", subnet.getCidr());
1440             } else {
1441                 String error = "Missing Required cidr for subnet in HEAT Template";
1442                 logger.error(LoggingAnchor.THREE, MessageEnum.RA_MISSING_PARAM, ErrorCode.DataError.getValue(), error);
1443                 throw new MsoAdapterException(error);
1444             }
1445
1446             if (subnet.getIpVersion() != null) {
1447                 curR = curR + "      ip_version: " + subnet.getIpVersion() + "\n";
1448             }
1449             if (subnet.getEnableDHCP() != null) {
1450                 curR = curR + "      enable_dhcp: " + Boolean.toString(subnet.getEnableDHCP()) + "\n";
1451             }
1452             if (subnet.getGatewayIp() != null && !subnet.getGatewayIp().isEmpty()) {
1453                 curR = curR + "      gateway_ip: " + subnet.getGatewayIp() + "\n";
1454             }
1455
1456             if (subnet.getAllocationPools() != null) {
1457                 StringBuilder tempBuf = new StringBuilder();
1458                 tempBuf.append(curR);
1459                 tempBuf.append("      allocation_pools:\n");
1460                 for (Pool pool : subnet.getAllocationPools()) {
1461                     if (!commonUtils.isNullOrEmpty(pool.getStart()) && !commonUtils.isNullOrEmpty(pool.getEnd())) {
1462                         tempBuf.append("       - start: ");
1463                         tempBuf.append(pool.getStart());
1464                         tempBuf.append("\n         end: ");
1465                         tempBuf.append(pool.getEnd());
1466                         tempBuf.append("\n");
1467                     }
1468                 }
1469                 curR = tempBuf.toString();
1470             }
1471
1472             resourcesBuf.append(curR);
1473
1474             curO = outputTempl;
1475             curO = curO.replace("%subnetId%", subnet.getSubnetId());
1476
1477             outputsBuf.append(curO);
1478         }
1479         // append resources and outputs in heatTemplate
1480         logger.debug("Tempate initial:{}", heatTemplate);
1481         int outputsIdx = heatTemplate.indexOf("outputs:");
1482         heatTemplate = insertStr(heatTemplate, outputsBuf.toString(), outputsIdx + 8);
1483         int resourcesIdx = heatTemplate.indexOf("resources:");
1484         heatTemplate = insertStr(heatTemplate, resourcesBuf.toString(), resourcesIdx + 10);
1485
1486         logger.debug("Template updated with all subnets:{}", heatTemplate);
1487         return heatTemplate;
1488     }
1489
1490     private Map<String, String> getSubnetUUId(String key, Map<String, Object> outputs, List<Subnet> subnets) {
1491
1492         Map<String, String> sMap = new HashMap<>();
1493
1494         try {
1495             Object obj = outputs.get(key);
1496             ObjectMapper mapper = new ObjectMapper();
1497             String jStr = mapper.writeValueAsString(obj);
1498             logger.debug("Subnet_Ipam Output JSON String:{} {}", obj.getClass(), jStr);
1499
1500             JsonNode rootNode = mapper.readTree(jStr);
1501             if (rootNode != null) {
1502                 for (JsonNode sNode : rootNode.path("ipam_subnets")) {
1503                     logger.debug("Output Subnet Node {}", sNode);
1504                     String name = sNode.path("subnet_name").textValue();
1505                     String uuid = sNode.path("subnet_uuid").textValue();
1506                     String aaiId = name; // default
1507                     // try to find aaiId for name in input subnetList
1508                     if (subnets != null) {
1509                         for (Subnet subnet : subnets) {
1510                             if (subnet != null && !commonUtils.isNullOrEmpty(subnet.getSubnetName())
1511                                     && subnet.getSubnetName().equals(name)) {
1512                                 aaiId = subnet.getSubnetId();
1513                                 break;
1514                             }
1515                         }
1516                     }
1517                     sMap.put(aaiId, uuid); // bpmn needs aaid to uuid map
1518                 }
1519             } else {
1520                 logger.error("{} {} null rootNode - cannot get subnet-uuids", MessageEnum.RA_MARSHING_ERROR,
1521                         ErrorCode.DataError.getValue());
1522             }
1523         } catch (Exception e) {
1524             logger.error("{} {} Exception getting subnet-uuids ", MessageEnum.RA_MARSHING_ERROR,
1525                     ErrorCode.DataError.getValue(), e);
1526         }
1527
1528         logger.debug("Return sMap {}", sMap);
1529         return sMap;
1530     }
1531
1532     private static String insertStr(String template, String snippet, int index) {
1533
1534         String updatedTemplate;
1535
1536         logger.debug("Index:{} Snippet:{}", index, snippet);
1537
1538         String templateBeg = template.substring(0, index);
1539         String templateEnd = template.substring(index);
1540
1541         updatedTemplate = templateBeg + "\n" + snippet + templateEnd;
1542
1543         logger.debug("Template updated with a subnet:{}", updatedTemplate);
1544         return updatedTemplate;
1545     }
1546
1547 }