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