diff --git a/drivers/deployment_drivers/openstack_nova_image_instance/drivermetadata.xml b/drivers/deployment_drivers/openstack_nova_image_instance/drivermetadata.xml
index d2dd8af..74fabb4 100644
--- a/drivers/deployment_drivers/openstack_nova_image_instance/drivermetadata.xml
+++ b/drivers/deployment_drivers/openstack_nova_image_instance/drivermetadata.xml
@@ -1,7 +1,7 @@
-
+
-
\ No newline at end of file
+
diff --git a/drivers/deployment_drivers/version.txt b/drivers/deployment_drivers/version.txt
index 43b2961..ceddfb2 100644
--- a/drivers/deployment_drivers/version.txt
+++ b/drivers/deployment_drivers/version.txt
@@ -1 +1 @@
-0.0.13
+0.0.15
diff --git a/drivers/openstack_shell/src/driver.py b/drivers/openstack_shell/src/driver.py
index 91c7aa8..f451d4f 100644
--- a/drivers/openstack_shell/src/driver.py
+++ b/drivers/openstack_shell/src/driver.py
@@ -29,6 +29,9 @@ def deploy_from_image(self, context, request):
# "cloud_provider_resource_name" : "openstack"},
# unpicklable=False))
+ def ApplyConnectivityChanges(self, context, request):
+ return self.os_shell.apply_connectivity(context, request)
+
def PowerOn(self, context, ports):
return self.os_shell.power_on(context)
diff --git a/drivers/openstack_shell/src/drivermetadata.xml b/drivers/openstack_shell/src/drivermetadata.xml
index 68db4b8..43503fc 100644
--- a/drivers/openstack_shell/src/drivermetadata.xml
+++ b/drivers/openstack_shell/src/drivermetadata.xml
@@ -1,10 +1,11 @@
-
+
+
@@ -15,4 +16,4 @@
-
\ No newline at end of file
+
diff --git a/drivers/openstack_shell/version.txt b/drivers/openstack_shell/version.txt
index 43b2961..9beca35 100644
--- a/drivers/openstack_shell/version.txt
+++ b/drivers/openstack_shell/version.txt
@@ -1 +1 @@
-0.0.13
+0.0.15
\ No newline at end of file
diff --git a/drivers/openstack_shellPackage/Configuration/shellconfig.xml b/drivers/openstack_shellPackage/Configuration/shellconfig.xml
index c72d317..0d8f377 100644
--- a/drivers/openstack_shellPackage/Configuration/shellconfig.xml
+++ b/drivers/openstack_shellPackage/Configuration/shellconfig.xml
@@ -15,6 +15,7 @@
+
diff --git a/drivers/openstack_shellPackage/DataModel/datamodel.xml b/drivers/openstack_shellPackage/DataModel/datamodel.xml
index 9676c01..9c34136 100644
--- a/drivers/openstack_shellPackage/DataModel/datamodel.xml
+++ b/drivers/openstack_shellPackage/DataModel/datamodel.xml
@@ -63,6 +63,14 @@
+
+
+
+
+
+
+
+
@@ -151,6 +159,9 @@
+
+
+
diff --git a/drivers/version.txt b/drivers/version.txt
index 43b2961..ceddfb2 100644
--- a/drivers/version.txt
+++ b/drivers/version.txt
@@ -1 +1 @@
-0.0.13
+0.0.15
diff --git a/package/cloudshell/cp/openstack/command/operations/connectivity_operation.py b/package/cloudshell/cp/openstack/command/operations/connectivity_operation.py
index 7eebb09..62b51fc 100644
--- a/package/cloudshell/cp/openstack/command/operations/connectivity_operation.py
+++ b/package/cloudshell/cp/openstack/command/operations/connectivity_operation.py
@@ -1,12 +1,21 @@
from cloudshell.cp.openstack.domain.services.nova.nova_instance_service import NovaInstanceService
+from cloudshell.cp.openstack.domain.services.neutron.neutron_network_service import NeutronNetworkService
from cloudshell.cp.openstack.domain.services.waiters.instance import InstanceWaiter
+from cloudshell.cp.openstack.common.deploy_data_holder import DeployDataHolder
+
+from cloudshell.cp.openstack.models.connectivity_action_result_model import ConnectivityActionResultModel
+from cloudshell.cp.openstack.models.driver_response_model import DriverResponse, DriverResponseRoot
+
+import jsonpickle
+
class ConnectivityOperation(object):
public_ip = "Public IP"
def __init__(self):
self.instance_waiter = InstanceWaiter()
self.instance_service = NovaInstanceService(self.instance_waiter)
+ self.network_service = NeutronNetworkService()
def refresh_ip(self, openstack_session, cloudshell_session,
deployed_app_resource, private_ip, resource_fullname,
@@ -38,3 +47,176 @@ def refresh_ip(self, openstack_session, cloudshell_session,
# FIXME : hardcoded public IP right now. Get it from floating IP later.
cloudshell_session.SetAttributeValue(resource_fullname, ConnectivityOperation.public_ip, "192.168.1.1")
+
+ def apply_connectivity(self, openstack_session, cp_resource_model, conn_request, logger):
+ """
+ Implements Apply connectivity - parses the conn_requests and creates
+ :param keystoneauth1.session.Session openstack_session:
+ :param OpenStackResourceModel cp_resource_model:
+ :param str conn_request: Connectivty Request JSON
+ :return DriverResponseRoot:
+ """
+
+ conn_req_deploy_data = DeployDataHolder(jsonpickle.decode(conn_request))
+
+ # Now collect following dict
+ # Key: (vlanID)
+ # value: List of (Resource_Name, VM_UUID, actionID)
+ # For each item, create network, and assign a nic on that network
+
+ actions = conn_req_deploy_data.driverRequest.actions
+
+ set_vlan_actions_dict = {}
+ remove_vlan_actions_dict = {}
+
+ # Add more description
+ # TODO : implement remove actions dict
+ for action in actions:
+ # FIXME: Move this "ifs into a separate function
+ if action.type == 'setVlan':
+ curr_dict = set_vlan_actions_dict
+ # FIXME: Check whether this is 'removeVlan'
+ else:
+ curr_dict = remove_vlan_actions_dict
+
+ action_vlanid = action.connectionParams.vlanId
+ actionid = action.actionId
+
+ deployed_app_res_name = action.actionTarget.fullName
+
+ for cust_attr in action.customActionAttributes :
+ if cust_attr.attributeName == 'VM_UUID':
+ vm_uuid = cust_attr.attributeValue
+ # FIXME : changed this to object for later readability
+ resource_info = (deployed_app_res_name, vm_uuid, actionid)
+ if action_vlanid in curr_dict.keys():
+ curr_dict[action_vlanid].append(resource_info)
+ else:
+ curr_dict[action_vlanid] = [resource_info]
+
+ results = []
+ if set_vlan_actions_dict:
+ result = self._do_set_vlan_actions(openstack_session=openstack_session,
+ cp_resource_model=cp_resource_model,
+ vlan_actions=set_vlan_actions_dict,
+ logger=logger)
+
+ results += result
+
+ if remove_vlan_actions_dict:
+ result = self._do_remove_vlan_actions(openstack_session=openstack_session,
+ cp_resource_model=cp_resource_model,
+ vlan_actions=set_vlan_actions_dict,
+ logger=logger)
+ results += result
+
+ # We have apply Connectivity results - We should send out the JSON and encode it
+ driver_response = DriverResponse()
+ driver_response.actionResults = results
+ driver_response_root = DriverResponseRoot()
+ driver_response_root.driverResponse = driver_response
+
+ return driver_response_root
+
+ def _do_set_vlan_actions(self, openstack_session, cp_resource_model, vlan_actions, logger):
+ """
+
+ :param keystoneauth1.session.Session openstack_session:
+ :param OpenStackResourceModel cp_resource_model:
+ :param dict vlan_actions:
+ :param LoggingSessionContext logger:
+ :return ConnectivityActionResult List :
+ """
+
+ # For each VLAN ID (create VLAN network)
+ results = []
+
+ for k, values in vlan_actions.iteritems():
+ # FIXME: results getting overwritten
+ # FIXME: update the nethod name
+ net = self.network_service.create_network_with_vlanid(openstack_session=openstack_session,
+ vlanid=int(k),
+ logger=logger)
+ if not net:
+ # FIXME : create error for the action
+ results = self._set_fail_results(values=values,
+ action_type='setVlan',
+ failure_text="Failed to Create Network with VLAN ID {0}".format(k))
+ else:
+ net_id = net['id']
+
+ subnet = net['subnets']
+ if not subnet:
+ # FIXME: Rename this function to create_and_attach
+ subnet = self.network_service.attach_subnet_to_net(openstack_session=openstack_session,
+ cp_resource_model=cp_resource_model,
+ net_id=net_id,
+ logger=logger)
+ else:
+ subnet = subnet[0]
+ if not subnet:
+ # FIXME: create error for action
+ results = self._set_fail_results(values=values,
+ action_type='setVlan',
+ failure_text="Failed to attach Subnet to Network {0}".format(net_id))
+ else:
+ attach_results = []
+ # FIXME: let's move this
+ for val in values:
+
+ instance_id = val[1]
+ # returns MAC Address of the attached port - which is reflected in updated Port
+ result = self.instance_service.attach_nic_to_net(openstack_session, instance_id, net_id, logger)
+ if not result:
+ action_result = ConnectivityActionResultModel()
+ action_result.success = False
+ action_result.actionId = val[2]
+ action_result.errorMessage = \
+ "Failed to Attach NIC on Network {0} to Instance {1}".format(net_id, val[0])
+ action_result.infoMessage = None
+ action_result.updatedInterface = None
+ else:
+ action_result = ConnectivityActionResultModel()
+ action_result.success = "True"
+ action_result.actionId = val[2]
+ action_result.errorMessage = ""
+ action_result.infoMessage = \
+ "Successfully Attached NIC on Network {0} to Instance {1}".format(net_id, val[0])
+ action_result.updatedInterface = result
+ action_result.type = 'setVlan'
+ attach_results.append(action_result)
+ results = attach_results
+ return results
+
+ def _do_remove_vlan_actions(self, openstack_session, cp_resource_model, vlan_actions, logger):
+ """
+ Function implementing Remove VLANs in apply_connectivity
+ :param keystoneauth1.session.Session openstack_session:
+ :param OpenStckResourceModel cp_resource_model:
+ :param dict vlan_actions:
+ :param LoggingSessionContext logger:
+ :return:
+ """
+ logger.info("_do_remove_vlan_actions called.")
+ return []
+
+ def _set_fail_results(self, values, failure_text, action_type, logger=None):
+ """
+ For all connections (obtained from values), set the failed results text, useful in generating output
+ :param tuple values:
+ :param str failure_text:
+ :param str action_type
+ :param logger:
+ :return ConnectivityActionResultModel List:
+ """
+ results = []
+ for value in values:
+ action_result = ConnectivityActionResultModel()
+ action_result.success = False
+ action_result.actionId = value[2]
+ action_result.infoMessage = None
+ action_result.errorMessage = failure_text
+ action_result.type = action_type
+ action_result.updatedInterface = None
+ results.append(action_result)
+ return results
\ No newline at end of file
diff --git a/package/cloudshell/cp/openstack/domain/services/neutron/neutron_network_service.py b/package/cloudshell/cp/openstack/domain/services/neutron/neutron_network_service.py
new file mode 100644
index 0000000..9ed7fdd
--- /dev/null
+++ b/package/cloudshell/cp/openstack/domain/services/neutron/neutron_network_service.py
@@ -0,0 +1,111 @@
+from neutronclient.v2_0 import client as neutron_client
+from neutronclient.common.exceptions import Conflict as NetCreateConflict
+
+class NeutronNetworkService(object):
+ """
+ A wrapper class around Neutron API
+ """
+
+ def __init__(self):
+ self.cidr_base = None
+ self.cidr_subnet_num = 0
+ self.allocated_subnets = []
+
+ def create_network_with_vlanid(self, openstack_session, vlanid, logger):
+ """
+
+ :param keystoneauth1.session.Session openstack_session:
+ :param int vlanid:
+ :param LoggingSessionContext logger:
+ :return dict :
+ """
+
+ client = neutron_client.Client(session=openstack_session)
+
+ nw_name = "net_vlanid_{0}".format(vlanid)
+ create_nw_json = {'provider:physical_network': 'public',
+ 'provider:network_type': 'vlan',
+ 'provider:segmentation_id': vlanid,
+ 'name': nw_name,
+ 'admin_state_up': True}
+
+ # FIXME : If an exception is raised - we just raise it all the way back? For now yes
+ try:
+ new_net = client.create_network({'network': create_nw_json})
+ new_net = new_net['network']
+ except NetCreateConflict:
+ new_net = client.list_networks(**{'provider:segmentation_id':vlanid})
+ new_net = new_net['networks'][0]
+ except Exception as e:
+ logger.error("Exception {0} Occurred while creating network".format(e))
+ return None
+
+ return new_net
+
+ def attach_subnet_to_net(self, openstack_session, cp_resource_model, net_id, logger):
+ """
+ Atttach a subnet to the network with given net_id.
+
+ :param keystoneauth1.session.Session openstack_session:
+ :param OpenStackResourceModel cp_resource_model:
+ :param str net_id: UUID string
+ :return dict:
+ """
+
+ client = neutron_client.Client(session=openstack_session)
+
+ cidr = self._get_unused_cidr(cp_resvd_cidrs=cp_resource_model.reserved_networks, logger=logger)
+ if cidr is None:
+ logger.error("Cannot allocate new subnet. All subnets exhausted")
+ return None
+
+ create_subnet_json = {'cidr': cidr,
+ 'network_id': net_id,
+ 'ip_version': 4}
+
+ try:
+ new_subnet = client.create_subnet({'subnet':create_subnet_json})
+ new_subnet = new_subnet['subnet']
+ except Exception as e:
+ logger.error("Exception {0} Occurred while creating network".format(e))
+ return None
+
+ return new_subnet
+
+ def _get_unused_cidr(self, cp_resvd_cidrs, logger):
+ """
+ Gets unused CIDR that excludes the reserved CIDRs
+ :param str cp_resvd_cidrs:
+ :return str:
+ """
+
+ # Algorithm below is a very simplistic one where we choose one of the three prefixes and then use
+ # /24 networks starting with that prefix. This algorithm will break if all three 10.X, 192.168.X and 172.X
+ # networks are used in a given On Prem Network.
+ # FIXME: Get subnets from openstack and not simply.
+ if self.cidr_base is not None:
+ cidr = ".".join([self.cidr_base, str(self.cidr_subnet_num), "0/24"])
+ if self.cidr_subnet_num not in self.allocated_subnets:
+ self.allocated_subnets.append(self.cidr_subnet_num)
+ self.cidr_subnet_num += 1
+ if self.cidr_subnet_num == 255:
+ self.cidr_subnet_num = 0
+ return cidr
+ else:
+ candidate_prefixes = {'10': '10.0', '192.168': '192.168', '172': '172.0'}
+ cp_resvd_cidrs = cp_resvd_cidrs.split(",")
+ logger.error(cp_resvd_cidrs)
+ possible_prefixes = filter(lambda x: any(map(lambda y: not y.strip().startswith(x), cp_resvd_cidrs)),
+ candidate_prefixes.keys())
+
+ logger.info(possible_prefixes)
+ if not possible_prefixes:
+ return None
+ else:
+ prefix = possible_prefixes[0]
+ self.cidr_base = candidate_prefixes[prefix]
+ cidr = ".".join([self.cidr_base, str(self.cidr_subnet_num), "0/24"])
+ self.allocated_subnets.append(self.cidr_subnet_num)
+ self.cidr_subnet_num += 1
+ return cidr
+ return None
\ No newline at end of file
diff --git a/package/cloudshell/cp/openstack/domain/services/nova/nova_instance_service.py b/package/cloudshell/cp/openstack/domain/services/nova/nova_instance_service.py
index 0a26efa..2858bba 100644
--- a/package/cloudshell/cp/openstack/domain/services/nova/nova_instance_service.py
+++ b/package/cloudshell/cp/openstack/domain/services/nova/nova_instance_service.py
@@ -181,3 +181,28 @@ def get_instance_from_instance_id(self, openstack_session, instance_id, logger,
return None
except Exception:
raise
+
+ def attach_nic_to_net(self, openstack_session, instance_id, net_id, logger):
+ """
+
+ :param openstack_session:
+ :param instance_id:
+ :param net_id:
+ :param logger:
+ :return:
+ """
+
+ instance = self.get_instance_from_instance_id(openstack_session=openstack_session,
+ instance_id=instance_id,
+ logger=logger)
+ if instance is None :
+ return None
+
+ try:
+ res = instance.interface_attach(net_id=net_id, port_id=None, fixed_ip=None)
+ iface_mac = res.to_dict().get('mac_addr')
+ return iface_mac
+ except Exception as e:
+ logger.info("Exception: {0} during interface attach".format(e))
+
+ return None
\ No newline at end of file
diff --git a/package/cloudshell/cp/openstack/models/connectivity_action_result_model.py b/package/cloudshell/cp/openstack/models/connectivity_action_result_model.py
new file mode 100644
index 0000000..e65977f
--- /dev/null
+++ b/package/cloudshell/cp/openstack/models/connectivity_action_result_model.py
@@ -0,0 +1,10 @@
+
+
+class ConnectivityActionResultModel(object):
+ def __init__(self):
+ self.actionId = None
+ self.success = None
+ self.infoMessage = None
+ self.errorMessage = None
+ self.type = None
+ self.updatedInterface = None
\ No newline at end of file
diff --git a/package/cloudshell/cp/openstack/models/driver_response_model.py b/package/cloudshell/cp/openstack/models/driver_response_model.py
new file mode 100644
index 0000000..9c0134b
--- /dev/null
+++ b/package/cloudshell/cp/openstack/models/driver_response_model.py
@@ -0,0 +1,8 @@
+
+class DriverResponse(object):
+ def __init__(self):
+ self.actionResults = []
+
+class DriverResponseRoot(object):
+ def __init__(self):
+ self.driverResponse = None
diff --git a/package/cloudshell/cp/openstack/models/model_parser.py b/package/cloudshell/cp/openstack/models/model_parser.py
index 9957750..bf8e246 100644
--- a/package/cloudshell/cp/openstack/models/model_parser.py
+++ b/package/cloudshell/cp/openstack/models/model_parser.py
@@ -25,6 +25,7 @@ def get_resource_model_from_context(resource):
os_res_model.os_user_password = attrs['OpenStack User Password']
os_res_model.qs_mgmt_os_net_uuid = attrs['Quali Management Network UUID']
os_res_model.os_floating_ip_pool = attrs['Floating IP Pool']
+ os_res_model.reserved_networks = attrs['Reserved Networks']
return os_res_model
@staticmethod
diff --git a/package/cloudshell/cp/openstack/models/openstack_resource_model.py b/package/cloudshell/cp/openstack/models/openstack_resource_model.py
index bd54187..7217d24 100644
--- a/package/cloudshell/cp/openstack/models/openstack_resource_model.py
+++ b/package/cloudshell/cp/openstack/models/openstack_resource_model.py
@@ -11,6 +11,7 @@ def __init__(self):
self.os_user_password = ''
self.qs_mgmt_os_net_uuid = ''
self.os_floating_ip_pool = ''
+ self.reserved_networks = ''
def __str__(self):
desc = "OpenStack Resource: controller_url: {0}, domain: {1}, project_name : {2}, os_user_name : {3}".format(
diff --git a/package/cloudshell/cp/openstack/openstack_shell.py b/package/cloudshell/cp/openstack/openstack_shell.py
index 5d52b39..b6835f6 100644
--- a/package/cloudshell/cp/openstack/openstack_shell.py
+++ b/package/cloudshell/cp/openstack/openstack_shell.py
@@ -82,6 +82,7 @@ def power_off(self, command_context):
:param cloudshell.shell.core.context.ResourceRemoteCommandContext command_context:
:rtype None:
"""
+
with LoggingSessionContext(command_context) as logger:
with ErrorHandlingContext(logger):
with CloudShellSessionContext(command_context) as cs_session:
@@ -214,4 +215,27 @@ def refresh_ip(self, command_context):
resource_fullname=deployed_app_fullname,
logger=logger)
+ def apply_connectivity(self, command_context, connectivity_request):
+ """
+
+ :param cloudshell.shell.core.context.ResourceRemoteCommandContext command_context:
+ :param str connectivity_request: Connectivity Request JSON string
+ :return:
+ """
+ with LoggingSessionContext(command_context) as logger:
+ with ErrorHandlingContext(logger):
+ with CloudShellSessionContext(command_context) as cs_session:
+ # FIXME: When implementing a context manager create all clients inside the contextManager.
+ logger.info(connectivity_request)
+ cp_resource_model = self.model_parser.get_resource_model_from_context(command_context.resource)
+
+ logger.debug(cp_resource_model)
+ os_session = self.os_session_provider.get_openstack_session(cs_session, cp_resource_model, logger)
+ connectivity_result = self.connectivity_operation.apply_connectivity(openstack_session=os_session,
+ cp_resource_model=cp_resource_model,
+ conn_request=connectivity_request,
+ logger=logger)
+
+ return self.command_result_parser.set_command_result(connectivity_result)
+
# Connectivity Operations End
diff --git a/package/requirements.txt b/package/requirements.txt
index 04c85a5..39832c7 100644
--- a/package/requirements.txt
+++ b/package/requirements.txt
@@ -2,4 +2,5 @@ cloudshell-automation-api>=7.0.0.0,<7.1.0.0
cloudshell-core>=2.1.0,<2.2.0
cloudshell-shell-core>=2.2.0,<2.3.0
python-novaclient>=4.0.8,<5.1.0
+python-neutronclient>=5.0.0,<=6.0.0
jsonpickle==0.9.3
diff --git a/package/tests/test_cp/test_openstack/test_models/test_model_parser.py b/package/tests/test_cp/test_openstack/test_models/test_model_parser.py
index 6755217..80d69a5 100644
--- a/package/tests/test_cp/test_openstack/test_models/test_model_parser.py
+++ b/package/tests/test_cp/test_openstack/test_models/test_model_parser.py
@@ -21,6 +21,7 @@ def test_get_resource_model_from_context(self):
test_resource.attributes['OpenStack User Password'] = 'test_pass'
test_resource.attributes['Quali Management Network UUID'] = '1234-56-78'
test_resource.attributes['Floating IP Pool'] = '10.0.0.100-10.0.0.101'
+ test_resource.attributes['Reserved Networks'] = '172.22.0.0/16'
result = self.tested_class.get_resource_model_from_context(test_resource)
@@ -32,6 +33,7 @@ def test_get_resource_model_from_context(self):
self.assertEqual(result.os_user_password, 'test_pass')
self.assertEqual(result.qs_mgmt_os_net_uuid, '1234-56-78')
self.assertEqual(result.os_floating_ip_pool, '10.0.0.100-10.0.0.101')
+ self.assertEqual(result.reserved_networks, '172.22.0.0/16')
@mock.patch("cloudshell.cp.openstack.models.model_parser.jsonpickle")
@mock.patch("cloudshell.cp.openstack.models.model_parser.DeployOSNovaImageInstanceResourceModel")
diff --git a/package/version.txt b/package/version.txt
index 43b2961..9beca35 100644
--- a/package/version.txt
+++ b/package/version.txt
@@ -1 +1 @@
-0.0.13
+0.0.15
\ No newline at end of file
diff --git a/version.txt b/version.txt
index 43b2961..ceddfb2 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.0.13
+0.0.15