CORD-1393 add allocate_public_service_instance to tenantwithcontainer
store policy status in db
Change-Id: I4a33abc3f237d3261d88fba098a3a089157b1961
diff --git a/xos/core/admin.py b/xos/core/admin.py
index f4f591a..d73320b 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -72,7 +72,7 @@
(icon, tooltip) = obj.get_backend_icon()
icon_url = ICON_URLS.get(icon, "unknown")
- return '<img src="%s"> %s' % (icon_url, tooltip)
+ return '<img src="%s"> POLICY=%s SYNC=%s' % (icon_url, obj.policy_status, tooltip)
class UploadTextareaWidget(AdminTextareaWidget):
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 197adc9..c460261 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -15,6 +15,7 @@
required bool lazy_blocked = 11 [default = False];
required bool no_sync = 12 [default = False];
required bool no_policy = 13 [default = False];
+ optional string policy_status = 14 [default = "0 - Policy in process", max_length = 1024];
}
message User (AbstractBaseUser,PlModelMixIn) {
diff --git a/xos/synchronizers/new_base/exceptions.py b/xos/synchronizers/new_base/exceptions.py
new file mode 100644
index 0000000..5aa8583
--- /dev/null
+++ b/xos/synchronizers/new_base/exceptions.py
@@ -0,0 +1,8 @@
+class SynchronizerException(Exception):
+ pass
+
+class SynchronizerProgrammingError(SynchronizerException):
+ pass
+
+class SynchronizerConfigurationError(SynchronizerException):
+ pass
diff --git a/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py b/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py
index b94e6b8..7a1b098 100644
--- a/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py
+++ b/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py
@@ -1,5 +1,6 @@
from synchronizers.new_base.modelaccessor import *
from synchronizers.new_base.policy import Policy
+from synchronizers.new_base.exceptions import *
class Scheduler(object):
# XOS Scheduler Abstract Base Class
@@ -70,17 +71,63 @@
# such as creating ports for containers.
instance.save()
+ def ip_to_mac(self, ip):
+ (a, b, c, d) = ip.split('.')
+ return "02:42:%02x:%02x:%02x:%02x" % (int(a), int(b), int(c), int(d))
+
+ def allocate_public_service_instance(self, **kwargs):
+ """ Get a ServiceInstance that provides public connectivity. Currently this means to use AddressPool and
+ VRouterTenant.
+
+ Expect this to be refactored when we break hard-coded service dependencies.
+ """
+ address_pool_name = kwargs.pop("address_pool_name")
+
+ vrouter_service = VRouterService.objects.all() # TODO: Hardcoded dependency
+ if not vrouter_service:
+ raise Exception("no vrouter services")
+ vrouter_service = vrouter_service[0]
+
+ ap = AddressPool.objects.filter(name=address_pool_name, service_id=vrouter_service.id)
+ if not ap:
+ raise Exception("vRouter unable to find addresspool %s" % name)
+ ap = ap[0]
+
+ ip = ap.get_address()
+ if not ip:
+ raise Exception("AddressPool '%s' has run out of addresses." % ap.name)
+
+ ap.save() # save the AddressPool to account for address being removed from it
+
+ # TODO: potential partial failure -- AddressPool address is allocated and saved before vrouter tenant
+
+ t = None
+ try:
+ t = VRouterTenant(provider_service=vrouter_service, **kwargs) # TODO: Hardcoded dependency
+ t.public_ip = ip
+ t.public_mac = self.ip_to_mac(ip)
+ t.address_pool_id = ap.id
+ t.save()
+ except:
+ # cleanup if anything went wrong
+ ap.put_address(ip)
+ if (t and t.id):
+ t.delete()
+ raise
+
+ return t
+
def get_image(self, tenant):
slice = tenant.provider_service.slices.all()
if not slice:
- raise XOSProgrammingError("provider service has no slice")
+ raise SynchronizerProgrammingError("provider service has no slice")
slice = slice[0]
# If slice has default_image set then use it
if slice.default_image:
return slice.default_image
- raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
+ raise SynchronizerProgrammingError("Please set a default image for %s" % self.slice.name)
""" get_legacy_tenant_attribute
pick_least_loaded_instance_in_slice
@@ -130,7 +177,7 @@
if tenant.instance is None:
if not tenant.provider_service.slices.count():
- raise XOSConfigurationError("The service has no slices")
+ raise SynchronizerConfigurationError("The service has no slices")
new_instance_created = False
instance = None
@@ -146,7 +193,7 @@
if not flavor:
flavors = Flavor.objects.filter(name="m1.small")
if not flavors:
- raise XOSConfigurationError("No m1.small flavor")
+ raise SynchronizerConfigurationError("No m1.small flavor")
flavor = flavors[0]
if slice.default_isolation == "container_vm":
diff --git a/xos/synchronizers/new_base/model_policies/test_config.yaml b/xos/synchronizers/new_base/model_policies/test_config.yaml
new file mode 100644
index 0000000..c05965e
--- /dev/null
+++ b/xos/synchronizers/new_base/model_policies/test_config.yaml
@@ -0,0 +1,4 @@
+name: test-model-policies
+accessor:
+ username: xosadmin@opencord.org
+ password: "sample"
diff --git a/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py b/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py
new file mode 100644
index 0000000..b137c44
--- /dev/null
+++ b/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py
@@ -0,0 +1,35 @@
+import unittest
+from mock import patch
+import mock
+
+import os, sys
+sys.path.append("../../..")
+sys.path.append("../../new_base/model_policies")
+config = basic_conf = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/test_config.yaml")
+from xosconfig import Config
+Config.init(config, 'synchronizer-config-schema.yaml')
+
+import synchronizers.new_base.modelaccessor
+
+from model_policy_tenantwithcontainer import TenantWithContainerPolicy
+
+class MockTenant:
+ provider_service = None
+ deleted = False
+ instance = None
+ service_specific_attribute = {}
+
+class TestModelPolicyVsgTenant(unittest.TestCase):
+ def setUp(self):
+ self.policy = TenantWithContainerPolicy()
+ self.tenant = MockTenant()
+
+ @patch.object(MockTenant, "provider_service")
+ def test_manage_container_no_slices(self, provider_service):
+ provider_service.slices.count.return_value = 0
+ with self.assertRaises(Exception) as e:
+ self.policy.manage_container(self.tenant)
+ self.assertEqual(e.exception.message, "The service has no slices")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/xos/synchronizers/new_base/model_policy_loop.py b/xos/synchronizers/new_base/model_policy_loop.py
index 74fb19a..48641b7 100644
--- a/xos/synchronizers/new_base/model_policy_loop.py
+++ b/xos/synchronizers/new_base/model_policy_loop.py
@@ -62,6 +62,9 @@
def load_model_policies(self, policies_dir):
policies=[]
for fn in os.listdir(policies_dir):
+ if fn.startswith("test"):
+ # don't try to import unit tests!
+ continue
pathname = os.path.join(policies_dir,fn)
if os.path.isfile(pathname) and fn.endswith(".py") and (fn!="__init__.py"):
module = imp.load_source(fn[:-3], pathname)
@@ -89,6 +92,7 @@
# These are the models whose children get deleted when they are
delete_policy_models = ['Slice','Instance','Network']
sender_name = getattr(instance, "model_name", instance.__class__.__name__)
+ new_policed = model_accessor.now()
#if (action != "deleted"):
# walk_inv_deps(self.update_dep, instance)
@@ -108,10 +112,17 @@
logger.log_exc("MODEL POLICY: Exception when running handler")
policies_failed = True
+ try:
+ instance.policy_status = "2 - %s" % traceback.format_exc(limit=1)
+ instance.save(update_fields=["policy_status"])
+ except:
+ logger.log_exc("MODEL_POLICY: Exception when storing policy_status")
+
if not policies_failed:
try:
- instance.policed=model_accessor.now()
- instance.save(update_fields=['policed'])
+ instance.policed=new_policed
+ instance.policy_status = "1 - done"
+ instance.save(update_fields=['policed', 'policy_status'])
except:
logger.log_exc('MODEL POLICY: Object %r failed to update policed timestamp' % instance)
diff --git a/xos/xos_client/xosapi/convenience/addresspool.py b/xos/xos_client/xosapi/convenience/addresspool.py
new file mode 100644
index 0000000..1653ec8
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/addresspool.py
@@ -0,0 +1,48 @@
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+
+class ORMWrapperAddressPool(ORMWrapper):
+ def get_address(self):
+ ap = self
+ if ap.addresses:
+ avail_ips = ap.addresses.split()
+ else:
+ avail_ips = []
+
+ if ap.inuse:
+ inuse_ips = ap.inuse.split()
+ else:
+ inuse_ips = []
+
+ while avail_ips:
+ addr = avail_ips.pop(0)
+
+ if addr in inuse_ips:
+ # This may have happened if someone re-ran the tosca
+ # recipe and 'refilled' the AddressPool while some addresses
+ # were still in use.
+ continue
+
+ inuse_ips.insert(0, addr)
+
+ ap.inuse = " ".join(inuse_ips)
+ ap.addresses = " ".join(avail_ips)
+ return addr
+
+ return None
+
+
+ def put_address(self, addr):
+ ap = self
+ addresses = ap.addresses or ""
+ parts = addresses.split()
+ if addr not in parts:
+ parts.insert(0, addr)
+ ap.addresses = " ".join(parts)
+
+ inuse = ap.inuse or ""
+ parts = inuse.split()
+ if addr in parts:
+ parts.remove(addr)
+ ap.inuse = " ".join(parts)
+
+register_convenience_wrapper("AddressPool", ORMWrapperAddressPool)
diff --git a/xos/xos_client/xosapi/convenience/vroutertenant.py b/xos/xos_client/xosapi/convenience/vroutertenant.py
index a4cd825..0a59b85 100644
--- a/xos/xos_client/xosapi/convenience/vroutertenant.py
+++ b/xos/xos_client/xosapi/convenience/vroutertenant.py
@@ -30,6 +30,7 @@
return None
# Use for tenant_for_instance_id
+ # TODO: These should be reimplemented using real database models
def get_attribute(self, name, default=None):
if self.service_specific_attribute:
@@ -38,5 +39,13 @@
attributes = {}
return attributes.get(name, default)
+ def set_attribute(self, name, value):
+ if self.service_specific_attribute:
+ attributes = json.loads(self.service_specific_attribute)
+ else:
+ attributes = {}
+ attributes[name] = value
+ self.service_specific_attribute = json.dumps(attributes)
+
register_convenience_wrapper("VRouterTenant", ORMWrapperVRouterTenant)
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index 2665500..ce815b4 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -502,6 +502,7 @@
return cls(wrapped_class, *args, **kwargs)
+import convenience.addresspool
import convenience.instance
import convenience.cordsubscriberroot
import convenience.volttenant