aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE.TXT25
-rw-r--r--README.TXT8
-rw-r--r--heatbridge/AAIManager.py219
-rw-r--r--heatbridge/HeatBridge.py66
-rw-r--r--heatbridge/OpenstackContext.py16
-rw-r--r--heatbridge/OpenstackManager.py144
-rw-r--r--heatbridge/__init__.py4
-rw-r--r--setup.cfg5
-rw-r--r--setup.py10
-rw-r--r--tox.ini12
11 files changed, 511 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c9bd9f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.tox/*
+openecomp_heatbridge.egg-info/*
diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 0000000..4aa836c
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,25 @@
+ECOMP and OpenECOMP are trademarks and service marks of AT&T Intellectual Property.
+
+LICENSE.TXT file can be appended as follows:
+/*
+ * ============LICENSE_START==========================================
+ * ===================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * ===================================================================
+ * 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.
+ * ============LICENSE_END============================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ *
+ */ \ No newline at end of file
diff --git a/README.TXT b/README.TXT
new file mode 100644
index 0000000..9c2f259
--- /dev/null
+++ b/README.TXT
@@ -0,0 +1,8 @@
+OpenECOMP HeatBridge
+=======================
+
+
+
+Script to input heat stack information into aai
+
+to install locally, checkout this repo and then run 'pip install -e .' in the root \ No newline at end of file
diff --git a/heatbridge/AAIManager.py b/heatbridge/AAIManager.py
new file mode 100644
index 0000000..70f974b
--- /dev/null
+++ b/heatbridge/AAIManager.py
@@ -0,0 +1,219 @@
+from collections import defaultdict
+
+class AAIManager:
+ """AAIManager manages the connection state and interaction between an aai instance and the heatbridge."""
+
+ def __init__(self, context):
+ self.openstack_context = context;
+
+ def get_link(self, links, link_type):
+ for link in links:
+ if link['rel'] == link_type:
+ return link['href'];
+
+ def create_transactions(self, *args):
+ transactions = defaultdict(list);
+ for arg in args:
+ transactions['transactions'].extend(arg);
+ return transactions;
+
+ def create_put(self, *args):
+ put = defaultdict(list);
+ put['put'].extend(args);
+ return put;
+
+ def create_vserver_put(self, server_info_dict, volumes_dict):
+ #setup
+ put = dict();
+ body = dict();
+ put['body'] = body;
+
+ #required fields
+ put['uri'] = "/cloud-infrastructure/cloud-regions/cloud-region/" + self.openstack_context.owner \
+ + "/" + self.openstack_context.region + "/tenants/tenant/" + self.openstack_context.tenant \
+ + "/vservers/vserver/" + server_info_dict['id'];
+ body['vserver-id'] = server_info_dict['id'];
+ body['vserver-name'] = server_info_dict['name'];
+ body['vserver-name2'] = server_info_dict['name'];
+ body['prov-status'] = server_info_dict['status'];
+ body['vserver-selflink'] = self.get_link(server_info_dict['links'], "self");
+ #body['in-maint'];
+ #body['is-closed-loop-disabled'];
+ #body['resource-version'];
+
+ #optional fields
+ volumes = []
+ body['volumes'] = volumes
+ for volume in volumes_dict:
+ volumes['volume'] = self.create_volume(volume);
+
+ relations = [];
+ if self.__exists(server_info_dict['metadata'], 'vnf_id'):
+ data = self.__create_relationship_data("generic-vnf", "vnf-id", server_info_dict['metadata']['vnf_id']);
+ list = self.__create_relationship_data_list(data);
+ relations.append(self.__create_relationship("generic-vnf", list));
+ if self.__exists(server_info_dict['flavor'], 'id'):
+ data = self.__create_relationship_data("flavor", "flavor-id", server_info_dict['flavor']['id']);
+ data2 = self.__create_relationship_data("cloud-region", "cloud-owner", self.openstack_context.owner);
+ data3 = self.__create_relationship_data("cloud-region", "cloud-region-id", self.openstack_context.region);
+ list = self.__create_relationship_data_list(data, data2, data3);
+ relations.append(self.__create_relationship("flavor", list));
+ if self.__exists(server_info_dict['image'], 'id'):
+ data = self.__create_relationship_data("image", "image-id", server_info_dict['image']['id']);
+ data2 = self.__create_relationship_data("cloud-region", "cloud-owner", self.openstack_context.owner);
+ data3 = self.__create_relationship_data("cloud-region", "cloud-region-id", self.openstack_context.region);
+ list = self.__create_relationship_data_list(data, data2, data3);
+ relations.append(self.__create_relationship("image", list));
+ body['relationship-list'] = self.__create_relationship_list(relations);
+ return put
+
+ def __create_relationship_list(self, items):
+ rel_list = dict()
+ relationship = []
+ rel_list['relationship'] = relationship;
+ relationship.extend(items);
+ return rel_list;
+
+ def __create_relationship_data_list(self, *relationship_data):
+ relationship_data_list = [];
+ relationship_data_list.extend(relationship_data);
+ return relationship_data_list;
+
+ def __create_relationship(self, related_to, relationship_data_list):
+ relationship = dict();
+ relationship['related-to'] = related_to;
+ relationship['relationship-data'] = relationship_data_list
+ return relationship;
+
+ def __create_relationship_data(self, parent, key, value):
+ relationship_data = dict();
+ relationship_data['relationship-key'] = parent + "." + key;
+ relationship_data['relationship-value'] = value;
+ return relationship_data;
+
+ def create_volume(self, volume_dict):
+ #setup
+ volume = dict()
+ volume['volume-id'] = volume_dict['volume']['id']
+ #volume['volume-selflink']
+ #volume['resource-version']
+ #volume['relationship-list']
+ return volume;
+
+ def create_flavor_put(self, flavor_dict):
+ #setup
+ put = dict();
+ body = dict()
+ put['body'] = body
+
+ #required fields
+ put['uri'] = "/cloud-infrastructure/cloud-regions/cloud-region/" + self.openstack_context.owner \
+ + "/" + self.openstack_context.region + "/flavors/flavor/" + flavor_dict['id']
+ body['flavor-id'] = flavor_dict['id']
+ body['flavor-name'] = flavor_dict['name']
+ body['flavor-vcpus'] = flavor_dict['vcpus']
+ body['flavor-ram'] = flavor_dict['ram']
+ body['flavor-disk'] = flavor_dict['disk']
+ if flavor_dict['OS-FLV-EXT-DATA:ephemeral'] != "":
+ body['flavor-ephemeral'] = flavor_dict['OS-FLV-EXT-DATA:ephemeral']
+ if flavor_dict['swap'] != "":
+ body['flavor-swap'] = flavor_dict['swap']
+ body['flavor-selflink'] = self.get_link(flavor_dict['links'], "self");
+ #body['flavor-is-public'']
+ #body['flavor-disabled']
+ #body['relationship-list']
+ return put
+
+ def create_image_put(self, image_dict, server_info):
+ #setup
+ put = dict();
+ body = dict()
+ put['body'] = body
+ put['uri'] = "/cloud-infrastructure/cloud-regions/cloud-region/" + self.openstack_context.owner \
+ + "/" + self.openstack_context.region + "/images/image/" + image_dict['id']
+ body['image-id'] = image_dict['id']
+ body['image-name'] = image_dict['name']
+ body['image-selflink'] = self.get_link(server_info['image']['links'], "bookmark");
+ if self.__exists(image_dict, 'org.openstack__1__architecture'):
+ body['image-architecture'] = image_dict['org.openstack__1__architecture']
+ if self.__exists(image_dict, 'org.openstack__1__os_distro'):
+ body['image-name'] = image_dict['org.openstack__1__os_distro']
+ if self.__exists(image_dict, 'org.openstack__1__os_version'):
+ body['image-os-version'] = image_dict['org.openstack__1__os_version']
+ if self.__exists(image_dict, 'org.openstack__1__application_version'):
+ body['application-version'] = image_dict['org.openstack__1__application_version']
+ if self.__exists(image_dict, 'org.openstack__1__application_vendor'):
+ body['application-vendor'] = image_dict['org.openstack__1__application_vendor']
+ if self.__exists(image_dict, 'org.openstack__1__application'):
+ body['application'] = image_dict['org.openstack__1__application']
+ if self.__exists(image_dict, 'os_distro'):
+ body['image-os-distro'] = image_dict['os_distro']
+ #body['metadata']
+ #body['relationship-list'];
+ return put
+
+ def __exists(self, the_dict, key):
+ if key in the_dict and the_dict[key] != "":
+ return True;
+ else:
+ return False;
+
+ def create_l_interface_put(self, port_dict, server_info_dict):
+ #setup
+ port = port_dict['port']
+ put = dict();
+ body = dict()
+ put['body'] = body
+ put['uri'] = "/cloud-infrastructure/cloud-regions/cloud-region/" + self.openstack_context.owner \
+ + "/" + self.openstack_context.region + "/tenants/tenant/" + self.openstack_context.tenant \
+ + "/vservers/vserver/" + port['device_id'] + "/l-interfaces/l-interface/" + port['name']
+ body['interface-id'] = port['id']
+ body['interface-name'] = port['name']
+ body['macaddr'] = port['mac_address']
+ #optional fields
+ v4list = []
+ v6list = []
+ body['l3-interface-ipv4-address-list'] = v4list
+ body['l3-interface-ipv6-address-list'] = v6list
+ for ips in port['fixed_ips']:
+ if '.' in ips['ip_address']:
+ v4list.append(self.create_l3_interface_ipv4_address(port, ips));
+ if ':' in ips['ip_address']:
+ v6list.append(self.create_l3_interface_ipv6_address(port, ips));
+ body['network-name'] = port['network_id']
+ #body['selflink']
+ #body['interface-role']
+ #body['v6-wan-link-ip']
+ #body['management-option']
+
+ #optional fields
+ return put
+
+ def create_l3_interface_ipv4_address(self, port_dict, fixed_ip):
+ #setup
+ address = dict()
+ address['l3-interface-ipv4-address'] = fixed_ip['ip_address']
+ address['l3-interface-ipv4-prefix-length'] = '32'
+ address['neutron-network-id'] = port_dict['network_id']
+ address['neutron-subnet-id'] = fixed_ip['subnet_id']
+ #address['vlan-id-inner']
+ #address['vlan-id-outer']
+ #address['is-floating']
+ #address['relationship-list']
+ return address;
+
+ def create_l3_interface_ipv6_address(self, port_dict, fixed_ip):
+ #setup
+ address = dict()
+ address['l3-interface-ipv6-address'] = fixed_ip['ip_address']
+ address['l3-interface-ipv6-prefix-length'] = '128'
+ address['neutron-network-id'] = port_dict['network_id']
+ address['neutron-subnet-id'] = fixed_ip['subnet_id']
+ #address['vlan-id-inner']
+ #address['vlan-id-outer']
+ #address['is-floating']
+ #address['relationship-list']
+ return address;
+
+ def load_aai_data(self, request):
+ return True; \ No newline at end of file
diff --git a/heatbridge/HeatBridge.py b/heatbridge/HeatBridge.py
new file mode 100644
index 0000000..f9b9580
--- /dev/null
+++ b/heatbridge/HeatBridge.py
@@ -0,0 +1,66 @@
+import json
+from OpenstackManager import OpenstackManager
+from OpenstackContext import OpenstackContext
+from AAIManager import AAIManager
+
+class HeatBridge:
+ def __init__(self):
+ pass;
+
+ def init_bridge(self, openstack_identity_url, username, password, tenant, region, owner):
+ self.om = OpenstackManager(openstack_identity_url, OpenstackContext(username, password, tenant, region, owner));
+ self.am = AAIManager(OpenstackContext(username, password, tenant, region, owner));
+
+ def filterbyvalue(self, seq, key, value):
+ for el in seq:
+ if el[key]==value: yield el
+
+ def build_request(self, heat_stack_id):
+ resources = self.om.get_stack_resources(heat_stack_id)
+ servers = list(self.filterbyvalue(resources, "resource_type", "OS::Nova::Server"));
+ #networks = list(self.filterbyvalue(resources, "resource_type", "OS::Neutron::Net"));
+ #subnets = list(self.filterbyvalue(resources, "resource_type", "OS::Neutron::Subnet"));
+ ports = list(self.filterbyvalue(resources, "resource_type", "OS::Neutron::Port"));
+ #keys = list(self.filterbyvalue(resources, "resource_type", "OS::Nova::KeyPair"));
+
+ put_blocks = []
+
+ #build the servers and attach them to vnf
+ server_put_blocks = []
+ image_put_blocks = []
+ flavor_put_blocks = []
+ for item in servers:
+ server_info = self.om.get_server_info(item['physical_resource_id']);
+ server_volumes = self.om.get_server_volumes(item['physical_resource_id']);
+ volumes = [];
+ for vols in server_volumes:
+ volumes.append(self.om.get_volume_info(vols['id']));
+ aai_vserver = self.am.create_vserver_put(server_info, volumes);
+ flavor_info = self.om.get_flavor_info(server_info['flavor']['id']);
+ aai_flavor = self.am.create_flavor_put(flavor_info);
+ image_info = self.om.get_image_info(server_info['image']['id']);
+ aai_image = self.am.create_image_put(image_info, server_info);
+ server_put_blocks.append(self.am.create_put(aai_vserver));
+ image_put_blocks.append(self.am.create_put(aai_image));
+ flavor_put_blocks.append(self.am.create_put(aai_flavor));
+ put_blocks.extend(image_put_blocks);
+ put_blocks.extend(flavor_put_blocks);
+ put_blocks.extend(server_put_blocks);
+
+ #build the ports and attach them to servers
+ linterface_put_blocks = []
+ #all servers have same vnf id
+ random_server_info = self.om.get_server_info(servers[0]['physical_resource_id']);
+ for item in ports:
+ #todo: pass in the networks from above
+ port_info = self.om.get_port_info(item['physical_resource_id'])
+ aai_linterface = self.am.create_l_interface_put(port_info, random_server_info);
+ linterface_put_blocks.append(self.am.create_put(aai_linterface));
+ put_blocks.extend(linterface_put_blocks);
+
+ return json.dumps(self.am.create_transactions(put_blocks));
+
+ def bridge_data(self, heat_stack_id):
+ request = self.build_request(heat_stack_id);
+ print request;
+ return request; \ No newline at end of file
diff --git a/heatbridge/OpenstackContext.py b/heatbridge/OpenstackContext.py
new file mode 100644
index 0000000..5e852a2
--- /dev/null
+++ b/heatbridge/OpenstackContext.py
@@ -0,0 +1,16 @@
+class OpenstackContext:
+ """OpenstackContext is a simple class that holds the provided information that heatbridge uses."""
+
+ #this holds the info of the openstack clients
+ username = None;
+ password = None;
+ tenant = None;
+ region = None;
+ owner = None;
+
+ def __init__(self, username, password, tenant, region, owner):
+ self.username = username;
+ self.password = password;
+ self.tenant = tenant;
+ self.region = region;
+ self.owner = owner; \ No newline at end of file
diff --git a/heatbridge/OpenstackManager.py b/heatbridge/OpenstackManager.py
new file mode 100644
index 0000000..ed4cd6f
--- /dev/null
+++ b/heatbridge/OpenstackManager.py
@@ -0,0 +1,144 @@
+from heatclient.v1.client import Client as HeatClient;
+from novaclient.v2.client import Client as NovaClient;
+from cinderclient.v1.client import Client as CinderClient;
+from glanceclient.v2.client import Client as GlanceClient;
+from neutronclient.v2_0.client import Client as NeutronClient;
+import os_client_config
+import logging
+
+class OpenstackManager:
+ """OpenstackManager manages the connection state and interaction between an openstack cloud and the heatbridge."""
+
+ #this holds the session of the openstack clients
+ __heat_client = None;
+ __nova_client = None;
+ __cinder_client = None;
+ __glance_client = None;
+ __neutron_client = None;
+ __auth_resp = None;
+
+ def __init__(self, identity_url, context):
+ """ OpenstackManager
+
+ `identity_url` Base identity_url of the identity server
+ 'context' Instance of OpenstackContext
+ """
+ self.openstack_context = context;
+ self.identity_url = identity_url;
+ self.authenticate(context.username, context.password, context.tenant, context.region)
+ logging.basicConfig(level=logging.DEBUG)
+
+ def authenticate(self, username, password, tenant, region):
+ """ Authenticate to openstack env
+
+ `username` username to authenticate to openstack
+
+ `password` password to send
+
+ `tenant` tenant to authenticate under
+
+ `region` region to authenticate under
+ """
+ self.__heat_client = os_client_config.make_client('orchestration',
+ auth_url=self.identity_url,
+ username=username,
+ password=password,
+ project_name=tenant,
+ region_name=region);
+ self.__nova_client = os_client_config.make_client('compute',
+ auth_url=self.identity_url,
+ username=username,
+ password=password,
+ project_name=tenant,
+ region_name=region);
+ self.__cinder_client = os_client_config.make_client('volume',
+ auth_url=self.identity_url,
+ username=username,
+ password=password,
+ project_name=tenant,
+ region_name=region);
+ self.__glance_client = os_client_config.make_client('image',
+ auth_url=self.identity_url,
+ username=username,
+ password=password,
+ project_name=tenant,
+ region_name=region);
+ self.__neutron_client = os_client_config.make_client('network',
+ auth_url=self.identity_url,
+ username=username,
+ password=password,
+ project_name=tenant,
+ region_name=region);
+ #this next line is needed because for v2 apis that are after a certain release stopped providing version info in keytone url but rackspace did not
+ self.__neutron_client.action_prefix = "";
+ self.__auth_resp = True;
+
+ def get_stack(self, stack_id):
+ self.__check_authenticated()
+ #: :type client: HeatClient
+ client = self.__heat_client
+ stack = client.stacks.get(stack_id)
+ return stack.to_dict();
+
+ def get_stack_resources(self, stack_id):
+ self.__check_authenticated()
+ #: :type client: HeatClient
+ client = self.__heat_client;
+ stack_resources = client.resources.list(stack_id);
+ stack_resources_dict = map(lambda x:x.to_dict(),stack_resources)
+ return stack_resources_dict;
+
+ def get_server_volumes(self, server_id):
+ self.__check_authenticated()
+ #: :type client: NovaClient
+ client = self.__nova_client;
+ server_volumes = client.volumes.get_server_volumes(server_id);
+ server_volumes_dict = map(lambda x:x.to_dict(),server_volumes)
+ return server_volumes_dict;
+
+ def get_server_interfaces(self, server_id):
+ self.__check_authenticated()
+ #: :type client: NovaClient
+ client = self.__nova_client;
+ server_interfaces = client.virtual_interfaces.list(server_id);
+ server_interfaces_dict = map(lambda x:x.to_dict(),server_interfaces)
+ return server_interfaces_dict;
+
+ def get_volume_info(self, volume_id):
+ self.__check_authenticated()
+ #: :type client: CinderClient
+ client = self.__cinder_client;
+ volume_info = client.volumes.get(volume_id);
+ return volume_info.to_dict();
+
+ def get_server_info(self, server_id):
+ self.__check_authenticated()
+ #: :type client: NovaClient
+ client = self.__nova_client;
+ server_info = client.servers.get(server_id);
+ return server_info.to_dict();
+
+ def get_image_info(self, image_id):
+ self.__check_authenticated()
+ #: :type client: GlanceClient
+ client = self.__glance_client;
+ image_info = client.images.get(image_id);
+ return image_info;
+
+ def get_flavor_info(self, flavor_id):
+ self.__check_authenticated()
+ #: :type client: NovaClient
+ client = self.__nova_client;
+ flavor_info = client.flavors.get(flavor_id);
+ return flavor_info.to_dict();
+
+ def get_port_info(self, port_id):
+ self.__check_authenticated()
+ #: :type client: NeutronClient
+ client = self.__neutron_client;
+ port_info = client.show_port(port_id);
+ return port_info;
+
+ def __check_authenticated(self):
+ if self.__auth_resp == None:
+ raise AssertionError('__auth_resp should exist before calling operation') \ No newline at end of file
diff --git a/heatbridge/__init__.py b/heatbridge/__init__.py
new file mode 100644
index 0000000..8be6f3b
--- /dev/null
+++ b/heatbridge/__init__.py
@@ -0,0 +1,4 @@
+class heatbridge:
+
+ def __init__(self):
+ pass;
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..493416b
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[bdist_wheel]
+# This flag says that the code is written to work on both Python 2 and Python
+# 3. If at all possible, it is good practice to do this. If you cannot, you
+# will need to generate wheels for each Python version that you support.
+universal=0 \ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..a43e2d2
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,10 @@
+from setuptools import setup
+
+setup(
+ name='openecomp-heatbridge', # This is the name of your PyPI-package.
+ version='0.1', # Update the version number for new releases
+ description='Script to input heat stack information into aai', # Info about script
+ install_requires=['python-novaclient','python-cinderclient','python-glanceclient', 'os_client_config', 'python-neutronclient', 'python-heatclient'], # what we need
+ packages=['heatbridge'], # The name of your scipts package
+ package_dir={'heatbridge': 'heatbridge'} # The location of your scipts package
+) \ No newline at end of file
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..09b4c98
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,12 @@
+# Tox (https://tox.readthedocs.io/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+