X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=rwcal%2Fplugins%2Fvala%2Frwcal_aws%2Frwcal_aws.py;fp=rwcal%2Fplugins%2Fvala%2Frwcal_aws%2Frwcal_aws.py;h=4f212d75c031bb79b67fb63185fecd9c06253c3d;hb=6f07e6f33f751ab4ffe624f6037f887b243bece2;hp=0000000000000000000000000000000000000000;hpb=72a563886272088feb7cb52e4aafbe6d2c580ff9;p=osm%2FSO.git diff --git a/rwcal/plugins/vala/rwcal_aws/rwcal_aws.py b/rwcal/plugins/vala/rwcal_aws/rwcal_aws.py new file mode 100644 index 00000000..4f212d75 --- /dev/null +++ b/rwcal/plugins/vala/rwcal_aws/rwcal_aws.py @@ -0,0 +1,1111 @@ + +# +# Copyright 2016 RIFT.IO Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import time +import os +import subprocess +import logging +import rift.rwcal.aws as aws_drv +import rw_status +import rift.cal.rwcal_status as rwcal_status +import rwlogger +import rift.rwcal.aws.exceptions as exceptions +from gi import require_version +require_version('RwCal', '1.0') + +from gi.repository import ( + GObject, + RwCal, + RwTypes, + RwcalYang) + + +PREPARE_VM_CMD = "prepare_vm.py --aws_key {key} --aws_secret {secret} --aws_region {region} --server_id {server_id}" +DELETE_VM_CMD = "delete_vm.py --aws_key {key} --aws_secret {secret} --aws_region {region} --server_id {server_id}" + +rwstatus_exception_map = {IndexError: RwTypes.RwStatus.NOTFOUND, + KeyError: RwTypes.RwStatus.NOTFOUND, + NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED, + AttributeError: RwTypes.RwStatus.FAILURE, + exceptions.RWErrorNotFound: RwTypes.RwStatus.NOTFOUND, + exceptions.RWErrorDuplicate: RwTypes.RwStatus.DUPLICATE, + exceptions.RWErrorExists: RwTypes.RwStatus.EXISTS, + exceptions.RWErrorNotConnected: RwTypes.RwStatus.NOTCONNECTED, + } + +rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map) +rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map) + +class RwcalAWSPlugin(GObject.Object, RwCal.Cloud): + """This class implements the CAL VALA methods for AWS.""" + + flavor_id = 1; + instance_num = 1 + def __init__(self): + GObject.Object.__init__(self) + self._driver_class = aws_drv.AWSDriver + self._flavor_list = [] + self.log = logging.getLogger('rwcal.aws.%s' % RwcalAWSPlugin.instance_num) + self.log.setLevel(logging.DEBUG) + + RwcalAWSPlugin.instance_num += 1 + + def _get_driver(self, account): + return self._driver_class(key = account.aws.key, + secret = account.aws.secret, + region = account.aws.region, + ssh_key = account.aws.ssh_key, + vpcid = account.aws.vpcid, + availability_zone = account.aws.availability_zone, + default_subnet_id = account.aws.default_subnet_id) + + @rwstatus + def do_init(self, rwlog_ctx): + self.log.addHandler( + rwlogger.RwLogger( + category="rw-cal-log", + subcategory="aws", + log_hdl=rwlog_ctx, + ) + ) + + @rwstatus(ret_on_failure=[None]) + def do_validate_cloud_creds(self, account): + """ + Validates the cloud account credentials for the specified account. + Performs an access to the resources using underlying API. If creds + are not valid, returns an error code & reason string + Arguments: + account - a cloud account to validate + + Returns: + Validation Code and Details String + """ + status = RwcalYang.CloudConnectionStatus( + status="success", + details="AWS Cloud Account validation not implemented yet" + ) + + return status + + @rwstatus(ret_on_failure=[""]) + def do_get_management_network(self, account): + """ + Returns the management network associated with the specified account. + Arguments: + account - a cloud account + + Returns: + The management network + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[""]) + def do_create_tenant(self, account, name): + """Create a new tenant. + + Arguments: + account - a cloud account + name - name of the tenant + + Returns: + The tenant id + """ + raise NotImplementedError + + @rwstatus + def do_delete_tenant(self, account, tenant_id): + """delete a tenant. + + Arguments: + account - a cloud account + tenant_id - id of the tenant + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[[]]) + def do_get_tenant_list(self, account): + """List tenants. + + Arguments: + account - a cloud account + + Returns: + List of tenants + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[""]) + def do_create_role(self, account, name): + """Create a new user. + + Arguments: + account - a cloud account + name - name of the user + + Returns: + The user id + """ + raise NotImplementedError + + @rwstatus + def do_delete_role(self, account, role_id): + """Delete a user. + + Arguments: + account - a cloud account + role_id - id of the user + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[[]]) + def do_get_role_list(self, account): + """List roles. + + Arguments: + account - a cloud account + + Returns: + List of roles + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[""]) + def do_create_image(self, account, image): + """Create an image + + Arguments: + account - a cloud account + image - a description of the image to create + + Returns: + The image id + """ + raise NotImplementedError + + @rwstatus + def do_delete_image(self, account, image_id): + """Delete a vm image. + + Arguments: + account - a cloud account + image_id - id of the image to delete + """ + raise NotImplementedError + + @staticmethod + def _fill_image_info(img_info): + """Create a GI object from image info dictionary + + Converts image information dictionary object returned by AWS + driver into Protobuf Gi Object + + Arguments: + account - a cloud account + img_info - image information dictionary object from AWS + + Returns: + The ImageInfoItem + """ + img = RwcalYang.ImageInfoItem() + img.name = img_info.name + img.id = img_info.id + + #tag_fields = ['checksum'] + # Look for any properties + if img_info.tags: + for tag in img_info.tags: + if tag['Key'] == 'checksum': + setattr(img, tag['Key'], tag['Value']) + img.disk_format = 'ami' + if img_info.state == 'available': + img.state = 'active' + else: + img.state = 'inactive' + return img + + @rwstatus(ret_on_failure=[[]]) + def do_get_image_list(self, account): + """Return a list of the names of all available images. + + Arguments: + account - a cloud account + + Returns: + The the list of images in VimResources object + """ + response = RwcalYang.VimResources() + images = self._get_driver(account).list_images() + for img in images: + response.imageinfo_list.append(RwcalAWSPlugin._fill_image_info(img)) + return response + + @rwstatus(ret_on_failure=[None]) + def do_get_image(self, account, image_id): + """Return a image information. + + Arguments: + account - a cloud account + image_id - an id of the image + + Returns: + ImageInfoItem object containing image information. + """ + image = self._get_driver(account).get_image(image_id) + return RwcalAWSPlugin._fill_image_info(image) + + @rwstatus(ret_on_failure=[""]) + def do_create_vm(self, account, vminfo): + """Create a new virtual machine. + + Arguments: + account - a cloud account + vminfo - information that defines the type of VM to create + + Returns: + The image id + """ + raise NotImplementedError + + @rwstatus + def do_start_vm(self, account, vm_id): + """Start an existing virtual machine. + + Arguments: + account - a cloud account + vm_id - an id of the VM + """ + raise NotImplementedError + + @rwstatus + def do_stop_vm(self, account, vm_id): + """Stop a running virtual machine. + + Arguments: + account - a cloud account + vm_id - an id of the VM + """ + raise NotImplementedError + + @rwstatus + def do_delete_vm(self, account, vm_id): + """Delete a virtual machine. + + Arguments: + account - a cloud account + vm_id - an id of the VM + """ + raise NotImplementedError + + @rwstatus + def do_reboot_vm(self, account, vm_id): + """Reboot a virtual machine. + + Arguments: + account - a cloud account + vm_id - an id of the VM + """ + raise NotImplementedError + + @staticmethod + def _fill_vm_info(vm_info): + """Create a GI object from vm info dictionary + + Converts VM information dictionary object returned by openstack + driver into Protobuf Gi Object + + Arguments: + vm_info - VM information from AWS + + Returns: + Protobuf Gi object for VM + """ + vm = RwcalYang.VMInfoItem() + vm.vm_id = vm_info.id + vm.image_id = vm_info.image_id + vm.flavor_id = vm_info.instance_type + if vm_info.state['Name'] == 'running': + vm.state = 'active' + else: + vm.state = 'inactive' + for network_intf in vm_info.network_interfaces: + if 'Attachment' in network_intf and network_intf['Attachment']['DeviceIndex'] == 0: + if 'Association' in network_intf and 'PublicIp' in network_intf['Association']: + vm.public_ip = network_intf['Association']['PublicIp'] + vm.management_ip = network_intf['PrivateIpAddress'] + else: + addr = vm.private_ip_list.add() + addr.ip_address = network_intf['PrivateIpAddress'] + if 'Association' in network_intf and 'PublicIp' in network_intf['Association']: + addr = vm.public_ip_list.add() + addr.ip_address = network_intf['Association']['PublicIp'] + + if vm_info.placement and 'AvailabilityZone' in vm_info.placement: + vm.availability_zone = vm_info.placement['AvailabilityZone'] + if vm_info.tags: + for tag in vm_info.tags: + if tag['Key'] == 'Name': + vm.vm_name = tag['Value'] + elif tag['Key'] in vm.user_tags.fields: + setattr(vm.user_tags,tag['Key'],tag['Value']) + return vm + + @rwstatus(ret_on_failure=[[]]) + def do_get_vm_list(self, account): + """Return a list of the VMs as vala boxed objects + + Arguments: + account - a cloud account + + Returns: + List containing VM information + """ + response = RwcalYang.VimResources() + vms = self._get_driver(account).list_instances() + for vm in vms: + response.vminfo_list.append(RwcalAWSPlugin._fill_vm_info(vm)) + return response + + @rwstatus(ret_on_failure=[None]) + def do_get_vm(self, account, id): + """Return vm information. + + Arguments: + account - a cloud account + id - an id for the VM + + Returns: + VM information + """ + vm = self._get_driver(account).get_instance(id) + return RwcalAWSPlugin._fill_vm_info(vm) + + @rwstatus(ret_on_failure=[""]) + def do_create_flavor(self, account, flavor): + """Create new flavor. + AWS has fixed set of AWS types and so we map flavor to existing instance type + and create local flavor for the same. + + Arguments: + account - a cloud account + flavor - flavor of the VM + + Returns: + flavor id (with EC2 instance type included in id) + """ + drv = self._get_driver(account) + inst_type = drv.map_flavor_to_instance_type(ram = flavor.vm_flavor.memory_mb, + vcpus = flavor.vm_flavor.vcpu_count, + disk = flavor.vm_flavor.storage_gb) + + new_flavor = RwcalYang.FlavorInfoItem() + new_flavor.name = flavor.name + new_flavor.vm_flavor.memory_mb = flavor.vm_flavor.memory_mb + new_flavor.vm_flavor.vcpu_count = flavor.vm_flavor.vcpu_count + new_flavor.vm_flavor.storage_gb = flavor.vm_flavor.storage_gb + new_flavor.id = inst_type + '-' + str(RwcalAWSPlugin.flavor_id) + RwcalAWSPlugin.flavor_id = RwcalAWSPlugin.flavor_id+1 + self._flavor_list.append(new_flavor) + return new_flavor.id + + @rwstatus + def do_delete_flavor(self, account, flavor_id): + """Delete flavor. + + Arguments: + account - a cloud account + flavor_id - id flavor of the VM + """ + + flavor = [flav for flav in self._flavor_list if flav.id == flavor_id] + self._flavor_list.delete(flavor[0]) + + @staticmethod + def _fill_flavor_info(flavor_info): + """Create a GI object from flavor info dictionary + + Converts Flavor information dictionary object returned by openstack + driver into Protobuf Gi Object + + Arguments: + flavor_info: Flavor information from openstack + + Returns: + Object of class FlavorInfoItem + """ + flavor = RwcalYang.FlavorInfoItem() + flavor.name = flavor_info.name + flavor.id = flavor_info.id + flavor.vm_flavor.memory_mb = flavor_info.vm_flavor.memory_mb + flavor.vm_flavor.vcpu_count = flavor_info.vm_flavor.vcpu_count + flavor.vm_flavor.storage_gb = flavor_info.vm_flavor.storage_gb + return flavor + + @rwstatus(ret_on_failure=[[]]) + def do_get_flavor_list(self, account): + """Return flavor information. + + Arguments: + account - a cloud account + + Returns: + List of flavors + """ + response = RwcalYang.VimResources() + for flv in self._flavor_list: + response.flavorinfo_list.append(RwcalAWSPlugin._fill_flavor_info(flv)) + return response + + + @rwstatus(ret_on_failure=[None]) + def do_get_flavor(self, account, id): + """Return flavor information. + + Arguments: + account - a cloud account + id - an id for the flavor + + Returns: + Flavor info item + """ + flavor = [flav for flav in self._flavor_list if flav.id == id] + return (RwcalAWSPlugin._fill_flavor_info(flavor[0])) + + def _fill_network_info(self, network_info, account): + """Create a GI object from network info dictionary + + Converts Network information dictionary object returned by AWS + driver into Protobuf Gi Object + + Arguments: + network_info - Network information from AWS + account - a cloud account + + Returns: + Network info item + """ + network = RwcalYang.NetworkInfoItem() + network.network_id = network_info.subnet_id + network.subnet = network_info.cidr_block + if network_info.tags: + for tag in network_info.tags: + if tag['Key'] == 'Name': + network.network_name = tag['Value'] + return network + + @rwstatus(ret_on_failure=[[]]) + def do_get_network_list(self, account): + """Return a list of networks + + Arguments: + account - a cloud account + + Returns: + List of networks + """ + response = RwcalYang.VimResources() + networks = self._get_driver(account).get_subnet_list() + for network in networks: + response.networkinfo_list.append(self._fill_network_info(network, account)) + return response + + @rwstatus(ret_on_failure=[None]) + def do_get_network(self, account, id): + """Return a network + + Arguments: + account - a cloud account + id - an id for the network + + Returns: + Network info item + """ + network = self._get_driver(account).get_subnet(id) + return self._fill_network_info(network, account) + + @rwstatus(ret_on_failure=[""]) + def do_create_network(self, account, network): + """Create a new network + + Arguments: + account - a cloud account + network - Network object + + Returns: + Network id + """ + raise NotImplementedError + + @rwstatus + def do_delete_network(self, account, network_id): + """Delete a network + + Arguments: + account - a cloud account + network_id - an id for the network + """ + raise NotImplementedError + + @staticmethod + def _fill_port_info(port_info): + """Create a GI object from port info dictionary + + Converts Port information dictionary object returned by AWS + driver into Protobuf Gi Object + + Arguments: + port_info - Port/Network interface information from AWS + + Returns: + Port info item + """ + port = RwcalYang.PortInfoItem() + + port.port_id = port_info.id + port.network_id = port_info.subnet_id + if port_info.attachment and 'InstanceId' in port_info.attachment: + port.vm_id = port_info.attachment['InstanceId'] + port.ip_address = port_info.private_ip_address + if port_info.status == 'in-use': + port.port_state = 'active' + elif port_info.status == 'available': + port.port_state = 'inactive' + else: + port.port_state = 'unknown' + if port_info.tag_set: + for tag in port_info.tag_set: + if tag['Key'] == 'Name': + port.port_name = tag['Value'] + return port + + + @rwstatus(ret_on_failure=[None]) + def do_get_port(self, account, port_id): + """Return a port + + Arguments: + account - a cloud account + port_id - an id for the port + + Returns: + Port info item + """ + port = self._get_driver(account).get_network_interface(port_id) + return RwcalAWSPlugin._fill_port_info(port) + + @rwstatus(ret_on_failure=[[]]) + def do_get_port_list(self, account): + """Return a list of ports + + Arguments: + account - a cloud account + + Returns: + Port info list + """ + response = RwcalYang.VimResources() + ports = self._get_driver(account).get_network_interface_list() + for port in ports: + response.portinfo_list.append(RwcalAWSPlugin._fill_port_info(port)) + return response + + @rwstatus(ret_on_failure=[""]) + def do_create_port(self, account, port): + """Create a new port + + Arguments: + account - a cloud account + port - port object + + Returns: + Port id + """ + raise NotImplementedError + + @rwstatus + def do_delete_port(self, account, port_id): + """Delete a port + + Arguments: + account - a cloud account + port_id - an id for port + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[""]) + def do_add_host(self, account, host): + """Add a new host + + Arguments: + account - a cloud account + host - a host object + + Returns: + An id for the host + """ + raise NotImplementedError + + @rwstatus + def do_remove_host(self, account, host_id): + """Remove a host + + Arguments: + account - a cloud account + host_id - an id for the host + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[None]) + def do_get_host(self, account, host_id): + """Return a host + + Arguments: + account - a cloud account + host_id - an id for host + + Returns: + Host info item + """ + raise NotImplementedError + + @rwstatus(ret_on_failure=[[]]) + def do_get_host_list(self, account): + """Return a list of hosts + + Arguments: + account - a cloud account + + Returns: + List of hosts + """ + raise NotImplementedError + + @rwcalstatus(ret_on_failure=[""]) + def do_create_virtual_link(self, account, link_params): + """Create a new virtual link + + Arguments: + account - a cloud account + link_params - information that defines the type of VDU to create + + Returns: + The vdu_id + """ + drv = self._get_driver(account) + kwargs = {} + kwargs['CidrBlock'] = link_params.subnet + + subnet = drv.create_subnet(**kwargs) + if link_params.name: + subnet.create_tags(Tags=[{'Key': 'Name','Value':link_params.name}]) + if link_params.associate_public_ip: + drv.modify_subnet(SubnetId=subnet.id,MapPublicIpOnLaunch=link_params.associate_public_ip) + return subnet.id + + @rwstatus + def do_delete_virtual_link(self, account, link_id): + """Delete a virtual link + + Arguments: + account - a cloud account + link_id - id for the virtual-link to be deleted + + Returns: + None + """ + drv = self._get_driver(account) + port_list = drv.get_network_interface_list(SubnetId=link_id) + for port in port_list: + if port and port.association and 'AssociationId' in port.association: + drv.disassociate_public_ip_from_network_interface(NetworkInterfaceId=port.id) + if port and port.attachment and 'AttachmentId' in port.attachment: + drv.detach_network_interface(AttachmentId = port.attachment['AttachmentId'],Force=True) #force detach as otherwise delete fails + #detach instance takes time; so poll to check port is not in-use + port = drv.get_network_interface(NetworkInterfaceId=port.id) + retries = 0 + while port.status == 'in-use' and retries < 10: + time.sleep(5) + port = drv.get_network_interface(NetworkInterfaceId=port.id) + drv.delete_network_interface(NetworkInterfaceId=port.id) + drv.delete_subnet(link_id) + + @staticmethod + def _fill_connection_point_info(c_point, port_info): + """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints() + + Converts EC2.NetworkInterface object returned by AWS driver into + Protobuf Gi Object + + Arguments: + port_info - Network Interface information from AWS + Returns: + Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints + """ + c_point.virtual_link_id = port_info.subnet_id + c_point.connection_point_id = port_info.id + if port_info.attachment: + c_point.vdu_id = port_info.attachment['InstanceId'] + c_point.ip_address = port_info.private_ip_address + if port_info.association and 'PublicIp' in port_info.association: + c_point.public_ip = port_info.association['PublicIp'] + if port_info.tag_set: + for tag in port_info.tag_set: + if tag['Key'] == 'Name': + c_point.name = tag['Value'] + if port_info.status == 'in-use': + c_point.state = 'active' + elif port_info.status == 'available': + c_point.state = 'inactive' + else: + c_point.state = 'unknown' + + @staticmethod + def _fill_virtual_link_info(network_info, port_list): + """Create a GI object for VirtualLinkInfoParams + + Converts Subnet and NetworkInterface object + returned by AWS driver into Protobuf Gi Object + + Arguments: + network_info - Subnet information from AWS + port_list - A list of network interface information from openstack + Returns: + Protobuf Gi object for VirtualLinkInfoParams + """ + link = RwcalYang.VirtualLinkInfoParams() + if network_info.state == 'available': + link.state = 'active' + else: + link.state = 'inactive' + link.virtual_link_id = network_info.subnet_id + link.subnet = network_info.cidr_block + if network_info.tags: + for tag in network_info.tags: + if tag['Key'] == 'Name': + link.name = tag['Value'] + for port in port_list: + c_point = link.connection_points.add() + RwcalAWSPlugin._fill_connection_point_info(c_point, port) + + return link + + @staticmethod + def _fill_vdu_info(vm_info, port_list): + """Create a GI object for VDUInfoParams + + Converts VM information dictionary object returned by AWS + driver into Protobuf Gi Object + + Arguments: + vm_info - EC2 instance information from AWS + port_list - A list of network interface information from AWS + Returns: + Protobuf Gi object for VDUInfoParams + """ + vdu = RwcalYang.VDUInfoParams() + vdu.vdu_id = vm_info.id + mgmt_port = [port for port in port_list if port.attachment and port.attachment['DeviceIndex'] == 0] + assert(len(mgmt_port) == 1) + vdu.management_ip = mgmt_port[0].private_ip_address + if mgmt_port[0].association and 'PublicIp' in mgmt_port[0].association: + vdu.public_ip = mgmt_port[0].association['PublicIp'] + #For now set managemnet ip also to public ip + #vdu.management_ip = vdu.public_ip + if vm_info.tags: + for tag in vm_info.tags: + if tag['Key'] == 'Name': + vdu.name = tag['Value'] + elif tag['Key'] == 'node_id': + vdu.node_id = tag['Value'] + vdu.image_id = vm_info.image_id + vdu.flavor_id = vm_info.instance_type + if vm_info.state['Name'] == 'running': + vdu.state = 'active' + else: + vdu.state = 'inactive' + #if vm_info.placement and 'AvailabilityZone' in vm_info.placement: + # vdu.availability_zone = vm_info.placement['AvailabilityZone'] + # Fill the port information + cp_port_list = [port for port in port_list if port.attachment and port.attachment['DeviceIndex'] != 0] + + for port in cp_port_list: + c_point = vdu.connection_points.add() + RwcalAWSPlugin._fill_connection_point_info(c_point, port) + return vdu + + + @rwstatus(ret_on_failure=[None]) + def do_get_virtual_link(self, account, link_id): + """Get information about virtual link. + + Arguments: + account - a cloud account + link_id - id for the virtual-link + + Returns: + Object of type RwcalYang.VirtualLinkInfoParams + """ + drv = self._get_driver(account) + network = drv.get_subnet(SubnetId=link_id) + port_list = drv.get_network_interface_list(SubnetId=link_id) + virtual_link = RwcalAWSPlugin._fill_virtual_link_info(network, port_list) + return virtual_link + + @rwstatus(ret_on_failure=[[]]) + def do_get_virtual_link_list(self, account): + """Get information about all the virtual links + + Arguments: + account - a cloud account + + Returns: + A list of objects of type RwcalYang.VirtualLinkInfoParams + """ + vnf_resources = RwcalYang.VNFResources() + drv = self._get_driver(account) + networks = drv.get_subnet_list() + for network in networks: + port_list = drv.get_network_interface_list(SubnetId=network.id) + virtual_link = RwcalAWSPlugin._fill_virtual_link_info(network, port_list) + vnf_resources.virtual_link_info_list.append(virtual_link) + return vnf_resources + + def _create_connection_point(self, account, c_point): + """ + Create a connection point + Arguments: + account - a cloud account + c_point - connection_points + """ + drv = self._get_driver(account) + port = drv.create_network_interface(SubnetId=c_point.virtual_link_id) + if c_point.name: + port.create_tags(Tags=[{'Key': 'Name','Value':c_point.name}]) + if c_point.associate_public_ip: + drv.associate_public_ip_to_network_interface(NetworkInterfaceId = port.id) + return port + + def prepare_vdu_on_boot(self, account, server_id,vdu_init_params,vdu_port_list = None): + cmd = PREPARE_VM_CMD.format(key = account.aws.key, + secret = account.aws.secret, + region = account.aws.region, + server_id = server_id) + if vdu_init_params.has_field('name'): + cmd += (" --vdu_name "+ vdu_init_params.name) + if vdu_init_params.has_field('node_id'): + cmd += (" --vdu_node_id "+ vdu_init_params.node_id) + if vdu_port_list is not None: + for port_id in vdu_port_list: + cmd += (" --vdu_port_list "+ port_id) + + exec_path = 'python3 ' + os.path.dirname(aws_drv.__file__) + exec_cmd = exec_path+'/'+cmd + self.log.info("Running command: %s" %(exec_cmd)) + subprocess.call(exec_cmd, shell=True) + + @rwcalstatus(ret_on_failure=[""]) + def do_create_vdu(self, account, vdu_init): + """Create a new virtual deployment unit + + Arguments: + account - a cloud account + vdu_init - information about VDU to create (RwcalYang.VDUInitParams) + + Returns: + The vdu_id + """ + drv = self._get_driver(account) + ### First create required number of ports aka connection points + port_list = [] + + ### Now Create VM + kwargs = {} + kwargs['ImageId'] = vdu_init.image_id + if vdu_init.has_field('flavor_id'): + #Get instance type from flavor id which is of form c3.xlarge-1 + inst_type = vdu_init.flavor_id.split('-')[0] + else: + inst_type = drv.map_flavor_to_instance_type(ram = vdu_init.vm_flavor.memory_mb, + vcpus = vdu_init.vm_flavor.vcpu_count, + disk = vdu_init.vm_flavor.storage_gb) + + kwargs['InstanceType'] = inst_type + if vdu_init.vdu_init and vdu_init.vdu_init.userdata: + kwargs['UserData'] = vdu_init.vdu_init.userdata + + #If we need to allocate public IP address create network interface and associate elastic + #ip to interface + if vdu_init.allocate_public_address: + port_id = drv.create_network_interface(SubnetId=drv.default_subnet_id) + drv.associate_public_ip_to_network_interface(NetworkInterfaceId = port_id.id) + network_interface = {'NetworkInterfaceId':port_id.id,'DeviceIndex':0} + kwargs['NetworkInterfaces'] = [network_interface] + + #AWS Driver will use default subnet id to create first network interface + # if network interface is not specified and will also have associate public ip + # if enabled for the subnet + vm_inst = drv.create_instance(**kwargs) + + # Wait for instance to get to running state before attaching network interface + # to instance + #vm_inst[0].wait_until_running() + + #if vdu_init.name: + #vm_inst[0].create_tags(Tags=[{'Key': 'Name','Value':vdu_init.name}]) + #if vdu_init.node_id is not None: + #vm_inst[0].create_tags(Tags=[{'Key':'node_id','Value':vdu_init.node_id}]) + + # Create the connection points + port_list = [] + for index,c_point in enumerate(vdu_init.connection_points): + port_id = self._create_connection_point(account, c_point) + port_list.append(port_id.id) + #drv.attach_network_interface(NetworkInterfaceId = port_id.id,InstanceId = vm_inst[0].id,DeviceIndex=index+1) + + # We wait for instance to get to running state and update name,node_id and attach network intfs + self.prepare_vdu_on_boot(account, vm_inst[0].id, vdu_init, port_list) + + return vm_inst[0].id + + @rwstatus + def do_modify_vdu(self, account, vdu_modify): + """Modify Properties of existing virtual deployment unit + + Arguments: + account - a cloud account + vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams) + """ + ### First create required number of ports aka connection points + drv = self._get_driver(account) + port_list = [] + + vm_inst = drv.get_instance(vdu_modify.vdu_id) + + if vm_inst.state['Name'] != 'running': + self.log.error("RWCAL-AWS: VM with id %s is not in running state during modify VDU",vdu_modify.vdu_id) + raise exceptions.RWErrorFailure("RWCAL-AWS: VM with id %s is not in running state during modify VDU",vdu_modify.vdu_id) + + port_list = drv.get_network_interface_list(InstanceId = vdu_modify.vdu_id) + used_device_indexs = [port.attachment['DeviceIndex'] for port in port_list if port.attachment] + + device_index = 1 + for c_point in vdu_modify.connection_points_add: + #Get unused device index + while device_index in used_device_indexs: + device_index = device_index+1 + port_id = self._create_connection_point(account, c_point) + drv.attach_network_interface(NetworkInterfaceId = port_id.id,InstanceId = vdu_modify.vdu_id,DeviceIndex =device_index) + + ### Detach the requested connection_points + for c_point in vdu_modify.connection_points_remove: + port = drv.get_network_interface(NetworkInterfaceId=c_point.connection_point_id) + #Check if elastic IP is associated with interface and release it + if port and port.association and 'AssociationId' in port.association: + drv.disassociate_public_ip_from_network_interface(NetworkInterfaceId=port.id) + if port and port.attachment and port.attachment['DeviceIndex'] != 0: + drv.detach_network_interface(AttachmentId = port.attachment['AttachmentId'],Force=True) #force detach as otherwise delete fails + else: + self.log.error("RWCAL-AWS: Cannot modify connection port at index 0") + + # Delete the connection points. Interfaces take time to get detached from instance and so + # we check status before doing delete network interface + for c_point in vdu_modify.connection_points_remove: + port = drv.get_network_interface(NetworkInterfaceId=c_point.connection_point_id) + retries = 0 + if port and port.attachment and port.attachment['DeviceIndex'] == 0: + self.log.error("RWCAL-AWS: Cannot modify connection port at index 0") + continue + while port.status == 'in-use' and retries < 10: + time.sleep(5) + port = drv.get_network_interface(NetworkInterfaceId=c_point.connection_point_id) + drv.delete_network_interface(port.id) + + def cleanup_vdu_on_term(self, account, server_id,vdu_port_list = None): + cmd = DELETE_VM_CMD.format(key = account.aws.key, + secret = account.aws.secret, + region = account.aws.region, + server_id = server_id) + if vdu_port_list is not None: + for port_id in vdu_port_list: + cmd += (" --vdu_port_list "+ port_id) + + exec_path = 'python3 ' + os.path.dirname(aws_drv.__file__) + exec_cmd = exec_path+'/'+cmd + self.log.info("Running command: %s" %(exec_cmd)) + subprocess.call(exec_cmd, shell=True) + + @rwstatus + def do_delete_vdu(self, account, vdu_id): + """Delete a virtual deployment unit + + Arguments: + account - a cloud account + vdu_id - id for the vdu to be deleted + + Returns: + None + """ + drv = self._get_driver(account) + ### Get list of port on VM and delete them. + #vm_inst = drv.get_instance(vdu_id) + + port_list = drv.get_network_interface_list(InstanceId = vdu_id) + delete_port_list = [port.id for port in port_list if port.attachment and port.attachment['DeleteOnTermination'] is False] + drv.terminate_instance(vdu_id) + + self.cleanup_vdu_on_term(account,vdu_id,delete_port_list) + + + @rwstatus(ret_on_failure=[None]) + def do_get_vdu(self, account, vdu_id): + """Get information about a virtual deployment unit. + + Arguments: + account - a cloud account + vdu_id - id for the vdu + + Returns: + Object of type RwcalYang.VDUInfoParams + """ + drv = self._get_driver(account) + + ### Get list of ports excluding the one for management network + vm = drv.get_instance(vdu_id) + port_list = drv.get_network_interface_list(InstanceId = vdu_id) + return RwcalAWSPlugin._fill_vdu_info(vm,port_list) + + + @rwstatus(ret_on_failure=[[]]) + def do_get_vdu_list(self, account): + """Get information about all the virtual deployment units + + Arguments: + account - a cloud account + + Returns: + A list of objects of type RwcalYang.VDUInfoParams + """ + vnf_resources = RwcalYang.VNFResources() + drv = self._get_driver(account) + vms = drv.list_instances() + for vm in vms: + ### Get list of ports excluding one for management network + port_list = [p for p in drv.get_network_interface_list(InstanceId = vm.id)] + vdu = RwcalAWSPlugin._fill_vdu_info(vm, + port_list) + vnf_resources.vdu_info_list.append(vdu) + return vnf_resources