Răsfoiți Sursa

Used OpenStack SDK to replace nova security groups with neutron security groups

Nuwan Goonasekera 8 ani în urmă
părinte
comite
339a79cff3

+ 5 - 4
cloudbridge/cloud/base/resources.py

@@ -733,10 +733,11 @@ class BaseVMFirewallRule(BaseCloudResource, VMFirewallRule):
         return self._name
 
     def __repr__(self):
-        return ("<CBVMFirewallRule: id: {0}; direction: {1}; protocol: {2};"
-                " from: {3}; to: {4}; cidr: {5}, src_dest_fw: {6}>"
-                .format(self.id, self.direction, self.protocol, self.from_port,
-                        self.to_port, self.cidr, self.src_dest_fw_id))
+        return ("<{0}: id: {1}; direction: {2}; protocol: {3};  from: {4};"
+                " to: {5}; cidr: {6}, src_dest_fw: {7}>"
+                .format(self.__class__.__name__, self.id, self.direction,
+                        self.protocol, self.from_port, self.to_port, self.cidr,
+                        self.src_dest_fw_id))
 
     def __eq__(self, other):
         return (isinstance(other, VMFirewallRule) and

+ 25 - 0
cloudbridge/cloud/providers/openstack/provider.py

@@ -17,6 +17,9 @@ from neutronclient.v2_0 import client as neutron_client
 from novaclient import client as nova_client
 from novaclient import shell as nova_shell
 
+from openstack import connection
+from openstack import profile
+
 from swiftclient import client as swift_client
 
 from .services import OpenStackBlockStoreService
@@ -59,6 +62,7 @@ class OpenStackCloudProvider(BaseCloudProvider):
         self._cinder = None
         self._swift = None
         self._neutron = None
+        self._os_conn = None
 
         # Additional cached variables
         self._cached_keystone_session = None
@@ -123,6 +127,21 @@ class OpenStackCloudProvider(BaseCloudProvider):
             self._cached_keystone_session = session.Session(auth=auth)
         return self._cached_keystone_session
 
+    def _connect_openstack(self):
+        prof = profile.Profile()
+        prof.set_region(profile.Profile.ALL, self.region_name)
+
+        return connection.Connection(
+            profile=prof,
+            user_agent='cloudbridge',
+            auth_url=self.auth_url,
+            project_name=self.project_name,
+            username=self.username,
+            password=self.password,
+            user_domain_name=self.user_domain_name,
+            project_domain_name=self.project_domain_name
+        )
+
 #     @property
 #     def glance(self):
 #         if not self._glance:
@@ -147,6 +166,12 @@ class OpenStackCloudProvider(BaseCloudProvider):
             self._neutron = self._connect_neutron()
         return self._neutron
 
+    @property
+    def os_conn(self):
+        if not self._os_conn:
+            self._os_conn = self._connect_openstack()
+        return self._os_conn
+
     @property
     def compute(self):
         return self._compute

+ 39 - 26
cloudbridge/cloud/providers/openstack/resources.py

@@ -42,6 +42,8 @@ from keystoneclient.v3.regions import Region
 
 import novaclient.exceptions as novaex
 
+from openstack.exceptions import HttpException
+
 import swiftclient
 
 from swiftclient.service import SwiftService, SwiftUploadObject
@@ -1020,8 +1022,11 @@ class OpenStackVMFirewall(BaseVMFirewall):
     def rules(self):
         return self._rule_svc
 
+    def delete(self):
+        return self._vm_firewall.delete(self._provider.os_conn.session)
+
     def refresh(self):
-        self._vm_firewall = self._provider.nova.security_groups.get(
+        self._vm_firewall = self._provider.os_conn.network.get_security_group(
             self.id)
 
     def to_json(self):
@@ -1041,7 +1046,7 @@ class OpenStackVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
     def list(self, limit=None, marker=None):
         # pylint:disable=protected-access
         rules = [OpenStackVMFirewallRule(self.firewall, r)
-                 for r in self.firewall._vm_firewall.rules]
+                 for r in self.firewall._vm_firewall.security_group_rules]
         return ClientPagedResultList(self._provider, rules,
                                      limit=limit, marker=marker)
 
@@ -1053,26 +1058,29 @@ class OpenStackVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
 
         try:
             if direction == TrafficDirection.INBOUND:
-                # pylint:disable=protected-access
-                rule = self._provider.nova.security_group_rules.create(
-                                parent_group_id=self.firewall.id,
-                                ip_protocol=protocol,
-                                from_port=from_port,
-                                to_port=to_port,
-                                cidr=cidr,
-                                group_id=src_dest_fw_id)
+                os_direction = 'ingress'
             elif direction == TrafficDirection.OUTBOUND:
-                pass
+                os_direction = 'egress'
             else:
                 raise InvalidValueException("direction", direction)
+            # pylint:disable=protected-access
+            rule = self._provider.os_conn.network.create_security_group_rule(
+                security_group_id=self.firewall.id,
+                direction=os_direction,
+                port_range_max=to_port,
+                port_range_min=from_port,
+                protocol=protocol,
+                remote_ip_prefix=cidr,
+                remote_group_id=src_dest_fw_id)
             self.firewall.refresh()
             return OpenStackVMFirewallRule(self.firewall, rule.to_dict())
-        except novaex.BadRequest as e:
+        except HttpException as e:
             self.firewall.refresh()
-            existing = self.find(
-                direction=direction, protocol=protocol, from_port=from_port,
-                to_port=to_port, cidr=cidr, src_dest_fw_id=src_dest_fw_id)
-            if existing:
+            # 409=Conflict, raised for duplicate rule
+            if e.http_status == 409:
+                existing = self.find(direction=direction, protocol=protocol,
+                                     from_port=from_port, to_port=to_port,
+                                     cidr=cidr, src_dest_fw_id=src_dest_fw_id)
                 return existing[0]
             else:
                 raise e
@@ -1089,23 +1097,29 @@ class OpenStackVMFirewallRule(BaseVMFirewallRule):
 
     @property
     def direction(self):
-        return TrafficDirection.INBOUND
+        direction = self._rule.get('direction')
+        if direction == 'ingress':
+            return TrafficDirection.INBOUND
+        elif direction == 'egress':
+            return TrafficDirection.OUTBOUND
+        else:
+            return None
 
     @property
     def protocol(self):
-        return self._rule.get('ip_protocol')
+        return self._rule.get('protocol')
 
     @property
     def from_port(self):
-        return self._rule.get('from_port')
+        return self._rule.get('port_range_min')
 
     @property
     def to_port(self):
-        return self._rule.get('to_port')
+        return self._rule.get('port_range_max')
 
     @property
     def cidr(self):
-        return self._rule.get('ip_range', {}).get('cidr')
+        return self._rule.get('remote_ip_prefix')
 
     @property
     def src_dest_fw_id(self):
@@ -1116,14 +1130,13 @@ class OpenStackVMFirewallRule(BaseVMFirewallRule):
 
     @property
     def src_dest_fw(self):
-        fw_name = self._rule.get('group', {}).get('name')
-        if fw_name:
-            fw = self._provider.security.vm_firewalls.find(name=fw_name)
-            return fw[0] if fw else None
+        fw_id = self._rule.get('remote_group_id')
+        if fw_id:
+            return self._provider.security.vm_firewalls.get(fw_id)
         return None
 
     def delete(self):
-        self._provider.nova.security_group_rules.delete(self.id)
+        self._provider.os_conn.network.delete_security_group_rule(self.id)
         self.firewall.refresh()
 
 

+ 11 - 7
cloudbridge/cloud/providers/openstack/services.py

@@ -40,6 +40,8 @@ from neutronclient.common.exceptions import NeutronClientException
 
 from novaclient.exceptions import NotFound as NovaNotFound
 
+from openstack.exceptions import ResourceNotFound
+
 from .resources import OpenStackBucket
 from .resources import OpenStackFloatingIP
 from .resources import OpenStackInstance
@@ -184,13 +186,14 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         try:
             return OpenStackVMFirewall(
                 self.provider,
-                self.provider.nova.security_groups.get(firewall_id))
-        except NovaNotFound:
+                self.provider.os_conn.network.get_security_group(firewall_id))
+        except ResourceNotFound:
             return None
 
     def list(self, limit=None, marker=None):
-        firewalls = [OpenStackVMFirewall(self.provider, fw)
-                     for fw in self.provider.nova.security_groups.list()]
+        firewalls = [
+            OpenStackVMFirewall(self.provider, fw)
+            for fw in self.provider.os_conn.network.security_groups()]
 
         return ClientPagedResultList(self.provider, firewalls,
                                      limit=limit, marker=marker)
@@ -198,15 +201,16 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
     def create(self, name, description, network_id):
         OpenStackVMFirewall.assert_valid_resource_name(name)
 
-        sg = self.provider.nova.security_groups.create(name, description)
+        sg = self.provider.os_conn.network.create_security_group(
+            name=name, description=description)
         if sg:
             return OpenStackVMFirewall(self.provider, sg)
         return None
 
     def find(self, name, limit=None, marker=None):
-        sgs = self.provider.nova.security_groups.findall(name=name)
+        sgs = [self.provider.os_conn.network.find_security_group(name)]
         results = [OpenStackVMFirewall(self.provider, sg)
-                   for sg in sgs]
+                   for sg in sgs if sg]
         return ClientPagedResultList(self.provider, results,
                                      limit=limit, marker=marker)
 

+ 1 - 0
setup.py

@@ -25,6 +25,7 @@ REQS_BASE = [
 ]
 REQS_AWS = ['boto3']
 REQS_OPENSTACK = [
+    'openstacksdk',
     'python-novaclient==7.0.0',
     'python-glanceclient>=2.5.0,<=2.6.0',
     'python-cinderclient>=1.9.0,<=2.0.1',

+ 3 - 0
test/helpers/__init__.py

@@ -1,6 +1,7 @@
 import functools
 import os
 import sys
+import traceback
 import unittest
 import uuid
 
@@ -46,11 +47,13 @@ def cleanup_action(cleanup_func):
             cleanup_func()
         except Exception as e:
             print("Error during exception cleanup: {0}".format(e))
+            traceback.print_exc()
         reraise(ex_class, ex_val, ex_traceback)
     try:
         cleanup_func()
     except Exception as e:
         print("Error during cleanup: {0}".format(e))
+        traceback.print_exc()
 
 
 def skipIfNoService(services):

+ 27 - 1
test/test_security_service.py

@@ -108,6 +108,29 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
                                create_fw_rule, cleanup_fw_rule,
                                skip_name_check=True)
 
+    @helpers.skipIfNoService(['security.vm_firewalls'])
+    def test_vm_firewall_rule_properties(self):
+        name = 'cb_propfwrule-{0}'.format(helpers.get_uuid())
+
+        # Declare these variables and late binding will allow
+        # the cleanup method access to the most current values
+        net = None
+        fw = None
+        with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
+                network=net, vm_firewall=fw)):
+            net, _ = helpers.create_test_network(self.provider, name)
+            fw = self.provider.security.vm_firewalls.create(
+                name=name, description=name, network_id=net.id)
+
+            rule = fw.rules.create(
+                direction=TrafficDirection.INBOUND, protocol='tcp',
+                from_port=1111, to_port=1111, cidr='0.0.0.0/0')
+            self.assertEqual(rule.direction, TrafficDirection.INBOUND)
+            self.assertEqual(rule.protocol, 'tcp')
+            self.assertEqual(rule.from_port, 1111)
+            self.assertEqual(rule.to_port, 1111)
+            self.assertEqual(rule.cidr, '0.0.0.0/0')
+
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_rule_add_twice(self):
         name = 'cb_fwruletwice-{0}'.format(helpers.get_uuid())
@@ -147,7 +170,10 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
                 name=name, description=name, network_id=net.id)
             rules = list(fw.rules)
             self.assertTrue(
-                len(rules) == 1, "Expected a single VM firewall rule allowing"
+                # TODO: This should be made consistent across all providers.
+                # Currently, OpenStack creates two rules, one for IPV6 and
+                # another for IPV4
+                len(rules) >= 1, "Expected a single VM firewall rule allowing"
                 " all outbound traffic. Got {0}.".format(rules))
             self.assertEqual(
                 rules[0].direction, TrafficDirection.OUTBOUND,