CORD-2648 convert core attics to decl
Change-Id: I7146244bfa468bbd9c9bba77708262749842aa53
diff --git a/lib/xos-genx/xos-genx-tests/test_jinja2_base.py b/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
new file mode 100644
index 0000000..0e7a2d4
--- /dev/null
+++ b/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
@@ -0,0 +1,35 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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 unittest
+from xosgenx.jinja2_extensions.base import *
+from helpers import FakeArgs, XProtoTestHelpers
+
+class Jinja2BaseTests(unittest.TestCase):
+ def test_xproto_is_true(self):
+ self.assertTrue(xproto_is_true(True))
+ self.assertTrue(xproto_is_true("True"))
+ self.assertTrue(xproto_is_true('"True"'))
+ self.assertFalse(xproto_is_true(False))
+ self.assertFalse(xproto_is_true("False"))
+ self.assertFalse(xproto_is_true('"False"'))
+ self.assertFalse(xproto_is_true(None))
+ self.assertFalse(xproto_is_true("something else"))
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/xosgenx/generator.py b/lib/xos-genx/xosgenx/generator.py
old mode 100755
new mode 100644
index 33e96b1..cc00742
--- a/lib/xos-genx/xosgenx/generator.py
+++ b/lib/xos-genx/xosgenx/generator.py
@@ -111,10 +111,9 @@
print "Saved: %s" % file_name
@staticmethod
- def _write_file_per_model(rendered, dir, extension, quiet):
+ def _write_file_per_model(rendered, dir, suffix, quiet):
for m in rendered:
-
- file_name = "%s/%s.%s" % (dir, m.lower(), extension)
+ file_name = "%s/%s%s" % (dir, m.lower(), suffix)
if not rendered[m]:
if quiet == False:
print "Not saving %s as it is empty" % file_name
@@ -186,7 +185,7 @@
# Validating
if args.write_to_file == 'single' and args.dest_file is None:
raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
- if args.write_to_file == 'model' and args.dest_extension is None:
+ if args.write_to_file == 'model' and (args.dest_extension is None):
raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
if args.output is not None and not os.path.isabs(args.output):
@@ -262,7 +261,11 @@
"options": v.options
}
)
- XOSProcessor._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
+ if (str(v.options.get("legacy", "false")).strip('"').lower() == "true"):
+ suffix = "_decl." + args.dest_extension
+ else:
+ suffix = "." + args.dest_extension
+ XOSProcessor._write_file_per_model(rendered, args.output, suffix, args.quiet)
else:
rendered = template.render(
{"proto":
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/base.py b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
index f8224b2..dd19bd8 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/base.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
@@ -272,3 +272,9 @@
return list
else:
return False
+
+def xproto_is_true(x):
+ # TODO: Audit xproto and make specification of trueness more uniform
+ if (x==True) or (x=="True") or (x=='"True"'):
+ return True
+ return False
diff --git a/lib/xos-genx/xosgenx/targets/django.xtarget b/lib/xos-genx/xosgenx/targets/django.xtarget
index ca81240..8ef8e5f 100644
--- a/lib/xos-genx/xosgenx/targets/django.xtarget
+++ b/lib/xos-genx/xosgenx/targets/django.xtarget
@@ -1,6 +1,23 @@
+{%- if options.legacy =='"True"' -%}
+{%- set legacy_tag = '_decl' -%}
+{%- set legacy = True -%}
+{%- else -%}
+{%- set legacy_tag = '' -%}
+{%- set legacy = False -%}
+{%- endif -%}
{% for m in proto.messages %}{% if not m.options.skip_django -%}
+{% if legacy %}
+{# handle models that use custom headers rather than deriving from xosbase #}
+{% if m.options.custom_header %}
+from {{ m.options.custom_header|replace('"','') }} import *
+{% else %}
+from core.models.xosbase import *
+{% endif %}
+{% else %}
{% if file_exists(xproto_base_name(m.name)|lower+'_header.py') -%}from {{xproto_base_name(m.name)|lower }}_header import *{%- else -%}from header import *{% endif %}
{% if file_exists(xproto_base_name(m.name)|lower+'_top.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_top.py') }} {% endif %}
+{% endif %}
+
{%- for l in m.links %}
{% if l.peer.name != m.name %}
@@ -24,7 +41,7 @@
{{ xproto_fol_to_python_validator(policy, proto.policies[policy], m, error) }}
{% endfor %}
-class {{ m.name }}{{ xproto_base_def(m.name, m.bases) }}:
+class {{ m.name }}{{ legacy_tag }}{{ xproto_base_def(m.name, m.bases) }}:
plural_name = "{{ xproto_pluralize(m) }}"
{# {% if m.options.no_sync or m.options.no_policy %}#}
@@ -53,11 +70,16 @@
{%- endfor %}
# Meta
+ class Meta:
{%- set uniques = xproto_field_graph_components(m.fields) %}
{%- if uniques %}
- class Meta:
unique_together = {{ xproto_tuplify(uniques) }}
{%- endif %}
+ {%- if xproto_is_true(m.options.abstract) %}
+ abstract=True
+ {%- endif %}
+ pass
+
{% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
pass
@@ -75,7 +97,7 @@
{% for policy,error in xproto_validations(m.options) %}
policy_{{policy}}_validator(self, None)
{% endfor %}
- super({{ m.name }}, self).save(*args, **kwds)
+ super({{ m.name }}{{ legacy_tag }}, self).save(*args, **kwds)
def can_access(self, ctx):
{% if m.policy %}
diff --git a/xos/core/models/addresspool.py b/xos/core/models/addresspool.py
new file mode 100644
index 0000000..dd8df6b
--- /dev/null
+++ b/xos/core/models/addresspool.py
@@ -0,0 +1,105 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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 socket
+import struct
+
+from xos.exceptions import *
+from addresspool_decl import *
+
+class AddressPool(AddressPool_decl):
+ class Meta:
+ proxy = True
+
+ def expand_cidr(self, cidr):
+ (network, bits) = cidr.split("/")
+ network = network.strip()
+ bits = int(bits.strip())
+
+ dest = []
+
+ netmask = (~(pow(2, 32 - bits) - 1) & 0xFFFFFFFF)
+
+ count = pow(2, 32 - bits)
+ for i in range(2, count - 1):
+ ip = struct.unpack("!L", socket.inet_aton(network))[0]
+ ip = ip & netmask | i
+ dest.append(socket.inet_ntoa(struct.pack("!L", ip)))
+
+ return (dest, bits)
+
+ def save(self, *args, **kwargs):
+ """
+ We need to convert subnets into lists of addresses before saving
+ """
+ if self.addresses and "/" in self.addresses:
+ original_addresses = self.addresses
+ (cidr_addrs, cidr_netbits) = self.expand_cidr(self.addresses)
+ self.addresses = " ".join(cidr_addrs)
+ if not self.cidr:
+ self.cidr = original_addresses
+
+ super(AddressPool, self).save(*args, **kwargs)
+
+ def get_address(self):
+ with transaction.atomic():
+ ap = AddressPool.objects.get(pk=self.pk)
+ 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)
+ ap.save()
+ return addr
+
+ addr = None
+ return addr
+
+ def put_address(self, addr):
+ with transaction.atomic():
+ ap = AddressPool.objects.get(pk=self.pk)
+ 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)
+
+ ap.save()
+
+
diff --git a/xos/core/models/attic/README.md b/xos/core/models/attic/README.md
deleted file mode 100644
index 784a8b6..0000000
--- a/xos/core/models/attic/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This directory contains legacy code that needs to be refactored and eliminated. Eventually, the attic will be empty.
diff --git a/xos/core/models/attic/address_top.py b/xos/core/models/attic/address_top.py
deleted file mode 100644
index bf71b64..0000000
--- a/xos/core/models/attic/address_top.py
+++ /dev/null
@@ -1,17 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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 socket
-import struct
diff --git a/xos/core/models/attic/addresspool_model.py b/xos/core/models/attic/addresspool_model.py
deleted file mode 100644
index 2aad18c..0000000
--- a/xos/core/models/attic/addresspool_model.py
+++ /dev/null
@@ -1,97 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-# imported in address_top.py
-# import socket
-# import struct
-
-def expand_cidr(self, cidr):
- (network, bits) = cidr.split("/")
- network = network.strip()
- bits = int(bits.strip())
-
- dest = []
-
- netmask = (~(pow(2, 32 - bits) - 1) & 0xFFFFFFFF)
-
- count = pow(2, 32 - bits)
- for i in range(2, count - 1):
- ip = struct.unpack("!L", socket.inet_aton(network))[0]
- ip = ip & netmask | i
- dest.append(socket.inet_ntoa(struct.pack("!L", ip)))
-
- return (dest, bits)
-
-def __xos_save_base(self, *args, **kwds):
- """
- We need to convert subnets into lists of addresses before saving
- """
- if self.addresses and "/" in self.addresses:
- original_addresses = self.addresses
- (cidr_addrs, cidr_netbits) = self.expand_cidr(self.addresses)
- self.addresses = " ".join(cidr_addrs)
- if not self.cidr:
- self.cidr = original_addresses
-
-def get_address(self):
- with transaction.atomic():
- ap = AddressPool.objects.get(pk=self.pk)
- 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)
- ap.save()
- return addr
-
- addr = None
- return addr
-
-def put_address(self, addr):
- with transaction.atomic():
- ap = AddressPool.objects.get(pk=self.pk)
- 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)
-
- ap.save()
-
-
diff --git a/xos/core/models/attic/controller_model.py b/xos/core/models/attic/controller_model.py
deleted file mode 100644
index b0fb0b2..0000000
--- a/xos/core/models/attic/controller_model.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
diff --git a/xos/core/models/attic/controllerslice_model.py b/xos/core/models/attic/controllerslice_model.py
deleted file mode 100644
index 026fc88..0000000
--- a/xos/core/models/attic/controllerslice_model.py
+++ /dev/null
@@ -1,24 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-def tologdict(self):
- d=super(ControllerSlice,self).tologdict()
- try:
- d['slice_name']=self.slice.name
- d['controller_name']=self.controller.name
- except:
- pass
- return d
diff --git a/xos/core/models/attic/deployment_model.py b/xos/core/models/attic/deployment_model.py
deleted file mode 100644
index 4bf0f3b..0000000
--- a/xos/core/models/attic/deployment_model.py
+++ /dev/null
@@ -1,47 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-def get_acl(self):
- return AccessControlList(self.accessControl)
-
-def test_acl(self, slice=None, user=None):
- potential_users=[]
-
- if user:
- potential_users.append(user)
-
- if slice:
- potential_users.append(slice.creator)
- for priv in slice.sliceprivileges.all():
- if priv.user not in potential_users:
- potential_users.append(priv.user)
-
- acl = self.get_acl()
- for user in potential_users:
- if acl.test(user) == "allow":
- return True
-
- return False
-
-@staticmethod
-def select_by_acl(user):
- ids = []
- for deployment in Deployment.objects.all():
- acl = deployment.get_acl()
- if acl.test(user) == "allow":
- ids.append(deployment.id)
-
- return Deployment.objects.filter(id__in=ids)
diff --git a/xos/core/models/attic/header.py b/xos/core/models/attic/header.py
deleted file mode 100644
index c65ca03..0000000
--- a/xos/core/models/attic/header.py
+++ /dev/null
@@ -1,87 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-from __future__ import absolute_import
-
-import sys
-import json
-import operator
-from operator import attrgetter
-from core.models.xosbase import *
-from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
-from django.contrib.contenttypes.models import ContentType
-from django.utils.timezone import now
-from core.acl import AccessControlList
-from django.core.exceptions import ValidationError,PermissionDenied
-
-from distutils.version import LooseVersion
-from django.db import models,transaction
-from django.db.models import *
-from django.core.validators import URLValidator
-from xos.exceptions import *
-import urlparse
-
-def ParseNatList(ports):
- """ Support a list of ports in the format "protocol:port, protocol:port, ..."
- examples:
- tcp 123
- tcp 123:133
- tcp 123, tcp 124, tcp 125, udp 201, udp 202
-
- User can put either a "/" or a " " between protocol and ports
- Port ranges can be specified with "-" or ":"
- """
- nats = []
- if ports:
- parts = ports.split(",")
- for part in parts:
- part = part.strip()
- if "/" in part:
- (protocol, ports) = part.split("/",1)
- elif " " in part:
- (protocol, ports) = part.split(None,1)
- else:
- raise TypeError('malformed port specifier %s, format example: "tcp 123, tcp 201:206, udp 333"' % part)
-
- protocol = protocol.strip()
- ports = ports.strip()
-
- if not (protocol in ["udp", "tcp"]):
- raise ValueError('unknown protocol %s' % protocol)
-
- if "-" in ports:
- (first, last) = ports.split("-")
- first = int(first.strip())
- last = int(last.strip())
- portStr = "%d:%d" % (first, last)
- elif ":" in ports:
- (first, last) = ports.split(":")
- first = int(first.strip())
- last = int(last.strip())
- portStr = "%d:%d" % (first, last)
- else:
- portStr = "%d" % int(ports)
-
- nats.append( {"l4_protocol": protocol, "l4_port": portStr} )
-
- return nats
-
-def ValidateNatList(ports):
- try:
- ParseNatList(ports)
- except Exception,e:
- raise ValidationError(str(e))
-
diff --git a/xos/core/models/attic/instance_model.py b/xos/core/models/attic/instance_model.py
deleted file mode 100644
index e29c26c..0000000
--- a/xos/core/models/attic/instance_model.py
+++ /dev/null
@@ -1,100 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-def get_controller (self):
- return self.node.site_deployment.controller
-
-def tologdict(self):
- d=super(Instance,self).tologdict()
- try:
- d['slice_name']=self.slice.name
- d['controller_name']=self.get_controller().name
- except:
- pass
- return d
-
-def __xos_save_base(self, *args, **kwds):
- if not self.name:
- self.name = self.slice.name
- if not self.creator and hasattr(self, 'caller'):
- self.creator = self.caller
-
-def all_ips(self):
- ips={}
- for ns in self.ports.all():
- if ns.ip:
- ips[ns.network.name] = ns.ip
- return ips
-
-def all_ips_string(self):
- result = []
- ips = self.all_ips()
- for key in sorted(ips.keys()):
- #result.append("%s = %s" % (key, ips[key]))
- result.append(ips[key])
- return ", ".join(result)
-all_ips_string.short_description = "addresses"
-
-def get_public_ip(self):
- for ns in self.ports.all():
- if (ns.ip) and (ns.network.template.visibility=="public") and (ns.network.template.translation=="none"):
- return ns.ip
- return None
-
-# return an address on nat-net
-def get_network_ip(self, pattern):
- for ns in self.ports.all():
- if pattern in ns.network.name.lower():
- return ns.ip
- return None
-
-# return an address that the synchronizer can use to SSH to the instance
-def get_ssh_ip(self):
- # first look specifically for a management_local network
- for ns in self.ports.all():
- if ns.network.template and ns.network.template.vtn_kind=="MANAGEMENT_LOCAL":
- return ns.ip
-
- # for compatibility, now look for any management network
- management=self.get_network_ip("management")
- if management:
- return management
-
- # if all else fails, look for nat-net (for OpenCloud?)
- return self.get_network_ip("nat")
-
-def get_ssh_command(self):
- if (not self.instance_id) or (not self.node) or (not self.instance_name):
- return None
- else:
- return 'ssh -o "ProxyCommand ssh -q %s@%s" ubuntu@%s' % (self.instance_id, self.node.name, self.instance_name)
-
-def get_public_keys(self):
- from core.models.sliceprivilege import Privilege
- slice_privileges = Privilege.objects.filter(object_id=self.slice.id, object_type='Slice', accessor_type='User')
- slice_users = [User.objects.get(pk = priv.accessor_id) for priv in slice_privileges]
- pubkeys = set([u.public_key for u in slice_users if u.public_key])
-
- if self.creator.public_key:
- pubkeys.add(self.creator.public_key)
-
- if self.slice.creator.public_key:
- pubkeys.add(self.slice.creator.public_key)
-
- if self.slice.service and self.slice.service.public_key:
- pubkeys.add(self.slice.service.public_key)
-
- return pubkeys
diff --git a/xos/core/models/attic/nodelabel_model.py b/xos/core/models/attic/nodelabel_model.py
deleted file mode 100644
index 67df5f9..0000000
--- a/xos/core/models/attic/nodelabel_model.py
+++ /dev/null
@@ -1,29 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-def __xos_save_base(self, *args, **kwds):
- """ Hack to allow the creation of NodeLabel objects from outside core
- until the ORM is extended with support for ManyToMany relations.
- """
-
- if self.name and '###' in self.name:
- from core.models import Node
-
- self.name, node_id_str = self.name.split('###')
- node_ids = map(int, node_id_str.split(','))
-
- for node_id in node_ids:
- node = Node.get(node_id)
- self.node.add(node)
diff --git a/xos/core/models/attic/service_header.py b/xos/core/models/attic/service_header.py
deleted file mode 100644
index 22ba19d..0000000
--- a/xos/core/models/attic/service_header.py
+++ /dev/null
@@ -1,202 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-from __future__ import absolute_import
-
-from core.models.xos import XOS
-from core.models.xosbase import *
-from django.core.validators import URLValidator
-import urlparse
-from operator import attrgetter
-import json
-from distutils.version import LooseVersion
-from django.core.validators import URLValidator
-from xos.exceptions import *
-
-
-COARSE_KIND = "coarse"
-
-def get_xos():
- xos = XOS.objects.all()
-
- if xos:
- return xos[0]
- else:
- return None
-
-class AttributeMixin(object):
- # helper for extracting things from a json-encoded
- # service_specific_attribute
-
- def get_attribute(self, name, default=None):
- if self.service_specific_attribute:
- attributes = json.loads(self.service_specific_attribute)
- else:
- 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)
-
- def get_initial_attribute(self, name, default=None):
- if self._initial["service_specific_attribute"]:
- attributes = json.loads(
- self._initial["service_specific_attribute"])
- else:
- attributes = {}
- return attributes.get(name, default)
-
- @classmethod
- def get_default_attribute(cls, name):
- for (attrname, default) in cls.simple_attributes:
- if attrname == name:
- return default
- if hasattr(cls, "default_attributes"):
- if name in cls.default_attributes:
- return cls.default_attributes[name]
-
- return None
-
- @classmethod
- def setup_simple_attributes(cls):
- for (attrname, default) in cls.simple_attributes:
- setattr(cls, attrname, property(lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
- lambda self, value, attrname=attrname: self.set_attribute(
- attrname, value),
- None,
- attrname))
-
-
-class Scheduler(object):
- # XOS Scheduler Abstract Base Class
- # Used to implement schedulers that pick which node to put instances on
-
- def __init__(self, slice):
- self.slice = slice
-
- def pick(self):
- # this method should return a tuple (node, parent)
- # node is the node to instantiate on
- # parent is for container_vm instances only, and is the VM that will
- # hold the container
-
- raise Exception("Abstract Base")
-
-
-class LeastLoadedNodeScheduler(Scheduler):
- # This scheduler always return the node with the fewest number of
- # instances.
-
- def __init__(self, slice, label=None):
- super(LeastLoadedNodeScheduler, self).__init__(slice)
- self.label = label
-
- def pick(self):
- from core.models import Node
-
- # start with all nodes
- nodes = Node.objects.all()
-
- # if a label is set, then filter by label
- if self.label:
- nodes = nodes.filter(nodelabels__name=self.label)
-
- # if slice.default_node is set, then filter by default_node
- if self.slice.default_node:
- nodes = nodes.filter(name = self.slice.default_node)
-
- # convert to list
- nodes = list(nodes)
-
- # sort so that we pick the least-loaded node
- nodes = sorted(nodes, key=lambda node: node.instances.all().count())
-
- if not nodes:
- raise Exception(
- "LeastLoadedNodeScheduler: No suitable nodes to pick from")
-
- # TODO: logic to filter nodes by which nodes are up, and which
- # nodes the slice can instantiate on.
-# nodes = sorted(nodes, key=lambda node: node.instances.all().count())
- return [nodes[0], None]
-
-
-class ContainerVmScheduler(Scheduler):
- # This scheduler picks a VM in the slice with the fewest containers inside
- # of it. If no VMs are suitable, then it creates a VM.
-
- MAX_VM_PER_CONTAINER = 10
-
- def __init__(self, slice):
- super(ContainerVmScheduler, self).__init__(slice)
-
- @property
- def image(self):
- from core.models import Image
-
- # If slice has default_image set then use it
- if self.slice.default_image:
- return self.slice.default_image
-
- raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
-
- def make_new_instance(self):
- from core.models import Instance, Flavor
-
- flavors = Flavor.objects.filter(name="m1.small")
- if not flavors:
- raise XOSConfigurationError("No m1.small flavor")
-
- (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
-
- instance = Instance(slice=self.slice,
- node=node,
- image=self.image,
- creator=self.slice.creator,
- deployment=node.site_deployment.deployment,
- flavor=flavors[0],
- isolation="vm",
- parent=parent)
- instance.save()
- # We rely on a special naming convention to identify the VMs that will
- # hole containers.
- instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
- instance.save()
- return instance
-
- def pick(self):
- from core.models import Instance, Flavor
-
- for vm in self.slice.instances.filter(isolation="vm"):
- avail_vms = []
- if (vm.name.startswith("%s-outer-" % self.slice.name)):
- container_count = Instance.objects.filter(parent=vm).count()
- if (container_count < self.MAX_VM_PER_CONTAINER):
- avail_vms.append((vm, container_count))
- # sort by least containers-per-vm
- avail_vms = sorted(avail_vms, key=lambda x: x[1])
- print "XXX", avail_vms
- if avail_vms:
- instance = avail_vms[0][0]
- return (instance.node, instance)
-
- instance = self.make_new_instance()
- return (instance.node, instance)
diff --git a/xos/core/models/attic/service_model.py b/xos/core/models/attic/service_model.py
deleted file mode 100644
index f7e8ca0..0000000
--- a/xos/core/models/attic/service_model.py
+++ /dev/null
@@ -1,185 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-KIND="generic"
-
-def __init__(self, *args, **kwargs):
- # for subclasses, set the default kind appropriately
- self._meta.get_field("kind").default = self.KIND
- super(Service, self).__init__(*args, **kwargs)
-
-@property
-def serviceattribute_dict(self):
- attrs = {}
- for attr in self.serviceattributes.all():
- attrs[attr.name] = attr.value
- return attrs
-
-def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
- """
- Get a list of nodes that can be used to scale up a slice.
-
- slice - slice to scale up
- max_per_node - maximum numbers of instances that 'slice' can have on a single node
- exclusive_slices - list of slices that must have no nodes in common with 'slice'.
- """
-
- # late import to get around order-of-imports constraint in __init__.py
- from core.models import Node, Instance
-
- nodes = list(Node.objects.all())
-
- conflicting_instances = Instance.objects.filter(
- slice__in=exclusive_slices)
- conflicting_nodes = Node.objects.filter(
- instances__in=conflicting_instances)
-
- nodes = [x for x in nodes if x not in conflicting_nodes]
-
- # If max_per_node is set, then limit the number of instances this slice
- # can have on a single node.
- if max_per_node:
- acceptable_nodes = []
- for node in nodes:
- existing_count = node.instances.filter(slice=slice).count()
- if existing_count < max_per_node:
- acceptable_nodes.append(node)
- nodes = acceptable_nodes
-
- return nodes
-
-def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
- # Pick the best node to scale up a slice.
-
- nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
- nodes = sorted(nodes, key=lambda node: node.instances.all().count())
- if not nodes:
- return None
- return nodes[0]
-
-def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
- # late import to get around order-of-imports constraint in __init__.py
- from core.models import Instance
-
- slices = [x for x in self.slices.all() if slice_hint in x.name]
- for slice in slices:
- while slice.instances.all().count() > scale:
- s = slice.instances.all()[0]
- # print "drop instance", s
- s.delete()
-
- while slice.instances.all().count() < scale:
- node = self.pick_node(slice, max_per_node, exclusive_slices)
- if not node:
- # no more available nodes
- break
-
- image = slice.default_image
- if not image:
- raise XOSConfigurationError(
- "No default_image for slice %s" % slice.name)
-
- flavor = slice.default_flavor
- if not flavor:
- raise XOSConfigurationError(
- "No default_flavor for slice %s" % slice.name)
-
- s = Instance(slice=slice,
- node=node,
- creator=slice.creator,
- image=image,
- flavor=flavor,
- deployment=node.site_deployment.deployment)
- s.save()
-
- # print "add instance", s
-
-def get_vtn_src_nets(self):
- nets = []
- for slice in self.slices.all():
- for ns in slice.networkslices.all():
- if not ns.network:
- continue
-# if ns.network.template.access in ["direct", "indirect"]:
-# # skip access networks; we want to use the private network
-# continue
- if "management" in ns.network.name:
- # don't try to connect the management network to anything
- continue
- if ns.network.name in ["wan_network", "lan_network"]:
- # we don't want to attach to the vCPE's lan or wan network
- # we only want to attach to its private network
- # TODO: fix hard-coding of network name
- continue
- for cn in ns.network.controllernetworks.all():
- if cn.net_id:
- net = {"name": ns.network.name, "net_id": cn.net_id}
- nets.append(net)
- return nets
-
-def get_vtn_nets(self):
- nets = []
- for slice in self.slices.all():
- for ns in slice.networkslices.all():
- if not ns.network:
- continue
- if ns.network.template.access not in ["direct", "indirect"]:
- # skip anything that's not an access network
- continue
- for cn in ns.network.controllernetworks.all():
- if cn.net_id:
- net = {"name": ns.network.name, "net_id": cn.net_id}
- nets.append(net)
- return nets
-
-def get_vtn_dependencies_nets(self):
- provider_nets = []
- for tenant in self.subscribed_tenants.all():
- if tenant.provider_service:
- for net in tenant.provider_service.get_vtn_nets():
- if not net in provider_nets:
- net["bidirectional"] = tenant.connect_method!="private-unidirectional"
- provider_nets.append(net)
- return provider_nets
-
-def get_vtn_dependencies_ids(self):
- return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
-
-def get_vtn_dependencies_names(self):
- return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
-
-def get_vtn_src_ids(self):
- return [x["net_id"] for x in self.get_vtn_src_nets()]
-
-def get_vtn_src_names(self):
- return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
-
-def get_composable_networks(self):
- SUPPORTED_VTN_SERVCOMP_KINDS = ['VSG','PRIVATE']
-
- nets = []
- for slice in self.slices.all():
- for net in slice.networks.all():
- if (net.template.vtn_kind not in SUPPORTED_VTN_SERVCOMP_KINDS) or (net.owner != slice):
- continue
-
- if not net.controllernetworks.exists():
- continue
- nets.append(net)
- return nets
-
-
-
diff --git a/xos/core/models/attic/serviceinstance_model.py b/xos/core/models/attic/serviceinstance_model.py
deleted file mode 100644
index f0006c1..0000000
--- a/xos/core/models/attic/serviceinstance_model.py
+++ /dev/null
@@ -1,59 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-def __init__(self, *args, **kwargs):
- super(ServiceInstance, self).__init__(*args, **kwargs)
-
-@property
-def tenantattribute_dict(self):
- attrs = {}
- for attr in self.tenantattributes.all():
- attrs[attr.name] = attr.value
- return attrs
-
-# helper function to be used in subclasses that want to ensure
-# service_specific_id is unique
-
-def validate_unique_service_specific_id(self, none_okay=False):
- if not none_okay and (self.service_specific_id is None):
- raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
- "service_specific_id": "cannot be none"})
-
- if self.service_specific_id:
- conflicts = self.__class__.objects.filter(
- service_specific_id=self.service_specific_id)
- if self.pk:
- conflicts = conflicts.exclude(pk=self.pk)
- if conflicts:
- raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
- "service_specific_id": "duplicate key"})
-
-def get_subscribed_tenants(self, tenant_class):
- """ Return all ServiceInstances of class tenant_class that have a link to this ServiceInstance """
- results=[]
- # TODO: Make query more efficient
- for si in tenant_class.objects.all():
- for link in si.subscribed_links.all():
- if link.provider_service_instance == self:
- results.append(si)
- return results
-
-def get_newest_subscribed_tenant(self, kind):
- st = list(self.get_subscribed_tenants(kind))
- if not st:
- return None
- return sorted(st, key=attrgetter('id'))[0]
-
diff --git a/xos/core/models/attic/serviceinstancelink_model.py b/xos/core/models/attic/serviceinstancelink_model.py
deleted file mode 100644
index dd0c2a5..0000000
--- a/xos/core/models/attic/serviceinstancelink_model.py
+++ /dev/null
@@ -1,47 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-def __xos_save_base(self, *args, **kwargs):
- subCount = sum([1 for e in [self.subscriber_service, self.subscriber_service_instance, self.subscriber_network] if e is not None])
- if (subCount > 1):
- raise XOSConflictingField(
- "Only one of subscriber_service, subscriber_service_instance, subscriber_network should be set")
-
- try:
- existing_instance = ServiceInstanceLink.objects.get(
- provider_service_instance=self.provider_service_instance,
- subscriber_service_instance=self.subscriber_service_instance,
- subscriber_service=self.subscriber_service,
- subscriber_network=self.subscriber_network
- )
-
- if (not self.pk and existing_instance) or (self.pk and self.pk != existing_instance.pk):
- raise XOSValidationError("A ServiceInstanceLink with attributes 'provider_service_instance=%s, subscriber_service_instance=%s, subscriber_service=%s, subscriber_network=%s' already exists"
- % (self.provider_service_instance, self.subscriber_service_instance, self.subscriber_service, self.subscriber_network))
- except self.DoesNotExist:
- # NOTE this is correct, no duplicated links
- pass
-
-
-def delete(self, *args, **kwargs):
- provider_service_instance = self.provider_service_instance
- super(ServiceInstanceLink, self).delete(*args, **kwargs)
-
- # This should be handled by a model_policy, but we don't currently have a
- # model policy for core objects, so handle it during the save method.
- if provider_service_instance and (not provider_service_instance.deleted):
- provider_service_instance.link_deleted_count += 1
- provider_service_instance.save(always_update_timestamp=True, update_fields=["updated", "link_deleted_count"])
diff --git a/xos/core/models/attic/slice_model.py b/xos/core/models/attic/slice_model.py
deleted file mode 100644
index c057f2a..0000000
--- a/xos/core/models/attic/slice_model.py
+++ /dev/null
@@ -1,43 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-NETWORK_CHOICES = ((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))
-
-@property
-def slicename(self):
- return "%s_%s" % (self.site.login_base, self.name)
-
-def __xos_save_base(self, *args, **kwds):
- # set creator on first save
- if not self.creator and hasattr(self, 'caller'):
- self.creator = self.caller
-
- # only admins change a slice's creator
- if 'creator' in self.changed_fields and \
- (not hasattr(self, 'caller') or not self.caller.is_admin):
-
- if (self._initial["creator"]==None) and (self.creator==getattr(self,"caller",None)):
- # it's okay if the creator is being set by the caller to
- # himeself on a new slice object.
- pass
- else:
- raise PermissionDenied("Insufficient privileges to change slice creator",
- {'creator': "Insufficient privileges to change slice creator"})
-
- if self.network=="Private Only":
- # "Private Only" was the default from the old Tenant View
- self.network=None
- self.enforce_choices(self.network, self.NETWORK_CHOICES)
diff --git a/xos/core/models/attic/tenant_model.py b/xos/core/models/attic/tenant_model.py
deleted file mode 100644
index a01233d..0000000
--- a/xos/core/models/attic/tenant_model.py
+++ /dev/null
@@ -1,62 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-KIND="generic"
-
-def __init__(self, *args, **kwargs):
- # for subclasses, set the default kind appropriately
- self._meta.get_field("kind").default = self.KIND
- super(Tenant, self).__init__(*args, **kwargs)
-
-@property
-def tenantattribute_dict(self):
- attrs = {}
- for attr in self.tenantattributes.all():
- attrs[attr.name] = attr.value
- return attrs
-
-# helper function to be used in subclasses that want to ensure
-# service_specific_id is unique
-def validate_unique_service_specific_id(self):
- if self.pk is None:
- if self.service_specific_id is None:
- raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
- "service_specific_id": "cannot be none"})
-
- conflicts = self.__class__.objects.filter(
- service_specific_id=self.service_specific_id)
- if conflicts:
- raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
- "service_specific_id": "duplicate key"})
-
-def __xos_save_base(self, *args, **kwargs):
- subCount = sum([1 for e in [self.subscriber_service, self.subscriber_tenant,
- self.subscriber_user, self.subscriber_root] if e is not None])
- if (subCount > 1):
- raise XOSConflictingField(
- "Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
-
-
-def get_subscribed_tenants(self, tenant_class):
- ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
- return tenant_class.objects.filter(id__in=ids)
-
-def get_newest_subscribed_tenant(self, kind):
- st = list(self.get_subscribed_tenants(kind))
- if not st:
- return None
- return sorted(st, key=attrgetter('id'))[0]
-
diff --git a/xos/core/models/attic/tenantroot_model.py b/xos/core/models/attic/tenantroot_model.py
deleted file mode 100644
index 388fac8..0000000
--- a/xos/core/models/attic/tenantroot_model.py
+++ /dev/null
@@ -1,49 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-KIND="generic"
-
-def __init__(self, *args, **kwargs):
- # for subclasses, set the default kind appropriately
- self._meta.get_field("kind").default = self.KIND
- super(TenantRoot, self).__init__(*args, **kwargs)
-
-def get_subscribed_tenants(self, tenant_class):
- ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
- return tenant_class.objects.filter(id__in=ids)
-
-def get_newest_subscribed_tenant(self, kind):
- st = list(self.get_subscribed_tenants(kind))
- if not st:
- return None
- return sorted(st, key=attrgetter('id'))[0]
-
-# helper function to be used in subclasses that want to ensure
-# service_specific_id is unique
-def validate_unique_service_specific_id(self, none_okay=False):
- if not none_okay and (self.service_specific_id is None):
- raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
- "service_specific_id": "cannot be none"})
-
- if self.service_specific_id:
- conflicts = self.__class__.objects.filter(
- service_specific_id=self.service_specific_id)
- if self.pk:
- conflicts = conflicts.exclude(pk=self.pk)
- if conflicts:
- raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
- "service_specific_id": "duplicate key"})
-
diff --git a/xos/core/models/attic/tenantwithcontainer_model.py b/xos/core/models/attic/tenantwithcontainer_model.py
deleted file mode 100644
index cebc461..0000000
--- a/xos/core/models/attic/tenantwithcontainer_model.py
+++ /dev/null
@@ -1,156 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-def __init__(self, *args, **kwargs):
- super(TenantWithContainer, self).__init__(*args, **kwargs)
-
- # vSG service relies on knowing when instance id has changed
- self.orig_instance_id = self.get_attribute("instance_id")
-
-# vSG service relies on instance_id attribute
-def get_attribute(self, name, default=None):
- if name=="instance_id":
- if self.instance:
- return self.instance.id
- else:
- return None
- else:
- return super(TenantWithContainer, self).get_attribute(name, default)
-
-# Services may wish to override the image() function to return different
-# images based on criteria in the tenant object. For example,
-# if (self.has_feature_A):
-# return Instance.object.get(name="image_with_feature_a")
-# elif (self.has_feature_B):
-# return Instance.object.get(name="image_with_feature_b")
-# else:
-# return super(MyTenantClass,self).image()
-
-@property
-def image(self):
- from core.models import Image
- # Implement the logic here to pick the image that should be used when
- # instantiating the VM that will hold the container.
-
- slice = self.provider_service.slices.all()
- if not slice:
- raise XOSProgrammingError("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)
-
-def save_instance(self, instance):
- # Override this function to do custom pre-save or post-save processing,
- # such as creating ports for containers.
- instance.save()
-
-def pick_least_loaded_instance_in_slice(self, slices, image):
- for slice in slices:
- if slice.instances.all().count() > 0:
- for instance in slice.instances.all():
- if instance.image != image:
- continue
- # Pick the first instance that has lesser than 5 tenants
- if self.count_of_tenants_of_an_instance(instance) < 5:
- return instance
- return None
-
-# TODO: Ideally the tenant count for an instance should be maintained using a
-# many-to-one relationship attribute, however this model being proxy, it does
-# not permit any new attributes to be defined. Find if any better solutions
-def count_of_tenants_of_an_instance(self, instance):
- tenant_count = 0
- for tenant in self.__class__.objects.all():
- if tenant.get_attribute("instance_id", None) == instance.id:
- tenant_count += 1
- return tenant_count
-
-def manage_container(self):
- from core.models import Instance, Flavor
-
- if self.deleted:
- return
-
- if (self.instance is not None) and (self.instance.image != self.image):
- self.instance.delete()
- self.instance = None
-
- if self.instance is None:
- if not self.provider_service.slices.count():
- raise XOSConfigurationError("The service has no slices")
-
- new_instance_created = False
- instance = None
- if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
- # Find if any existing instances can be used for this tenant
- slices = self.provider_service.slices.all()
- instance = self.pick_least_loaded_instance_in_slice(slices, self.image)
-
- if not instance:
- slice = self.provider_service.slices.all()[0]
-
- flavor = slice.default_flavor
- if not flavor:
- flavors = Flavor.objects.filter(name="m1.small")
- if not flavors:
- raise XOSConfigurationError("No m1.small flavor")
- flavor = flavors[0]
-
- if slice.default_isolation == "container_vm":
- (node, parent) = ContainerVmScheduler(slice).pick()
- else:
- (node, parent) = LeastLoadedNodeScheduler(slice).pick()
-
- instance = Instance(slice=slice,
- node=node,
- image=self.image,
- creator=self.creator,
- deployment=node.site_deployment.deployment,
- flavor=flavor,
- isolation=slice.default_isolation,
- parent=parent)
- self.save_instance(instance)
- new_instance_created = True
-
- try:
- self.instance = instance
- super(TenantWithContainer, self).save()
- except:
- if new_instance_created:
- instance.delete()
- raise
-
-def cleanup_container(self):
- if self.instance:
- if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
- # Delete the instance only if this is last tenant in that
- # instance
- tenant_count = self.count_of_tenants_of_an_instance(
- self.instance)
- if tenant_count == 0:
- self.instance.delete()
- else:
- self.instance.delete()
- self.instance = None
-
-def __xos_save_base(self, *args, **kwargs):
- if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
- self.creator = self.caller
-
diff --git a/xos/core/models/attic/xosbase_model.py b/xos/core/models/attic/xosbase_model.py
deleted file mode 100644
index 5fea7c8..0000000
--- a/xos/core/models/attic/xosbase_model.py
+++ /dev/null
@@ -1,193 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-
-
-objects = XOSBaseManager()
-deleted_objects = XOSBaseDeletionManager()
-
-class Meta:
- # Changing abstract to False would require the managers of subclasses of
- # XOSBase to be customized individually.
- abstract = True
- app_label = "core"
-
-def __init__(self, *args, **kwargs):
- super(XOSBase, self).__init__(*args, **kwargs)
- self._initial = self._dict # for PlModelMixIn
- self.silent = False
-
-def get_controller(self):
- return self.controller
-
-def delete(self, *args, **kwds):
- # so we have something to give the observer
- purge = kwds.get('purge',False)
- if purge:
- del kwds['purge']
- silent = kwds.get('silent',False)
- if silent:
- del kwds['silent']
- try:
- purge = purge or observer_disabled
- except NameError:
- pass
-
- if (purge):
- pk = self.pk
- super(XOSBase, self).delete(*args, **kwds)
- self.push_redis_event(deleted=True, pk=pk)
- else:
- if (not self.write_protect ):
- self.deleted = True
- self.enacted=None
- self.policed=None
- self.save(update_fields=['enacted','deleted','policed'], silent=silent)
-
- collector = XOSCollector(using=router.db_for_write(self.__class__, instance=self))
- collector.collect([self])
- with transaction.atomic():
- for (k, models) in collector.data.items():
- for model in models:
- if not hasattr(model, "deleted"):
- # Automatically generated through relations from ManyToMany fields do not have soft-delete
- # capability.
- continue
- if model.deleted:
- # in case it's already been deleted, don't delete again
- continue
- model.deleted = True
- model.enacted=None
- model.policed=None
- model.save(update_fields=['enacted','deleted','policed'], silent=silent)
-
-def verify_live_keys(self, update_fields):
- """ Check the fields to be updated, if they contain foreign keys, that the foreign keys only point
- to live objects in the database.
-
- This is to catch races between model policies where an object is being deleted while a model policy is
- still operating on it.
- """
-
- if getattr(self, "deleted", False):
- # If this model is already deleted, then no need to check anything. We only need to check for live
- # models that point to dead models. If a dead model points to other dead models, then we could
- # be updating something else in the dead model (backend_status, etc)
- return
-
- for field in self._meta.fields:
- try:
- f = getattr(self, field.name)
- except Exception, e:
- # Exception django.db.models.fields.related.RelatedObjectDoesNotExist
- # is thrown by django when you're creating an object that has a base and the base doesn't exist yet
- continue
-
- if f is None:
- # If field hold a null value, we don't care
- continue
-
- ftype = field.get_internal_type()
- if (ftype != "ForeignKey"):
- # If field isn't a foreign key, we don't care
- continue
-
- if (update_fields) and (field.name not in update_fields):
- # If update_fields is nonempty, and field is not to be updated, we don't care.
- continue
-
- if getattr(f, "deleted", False):
- raise Exception("Attempt to save object with deleted foreign key reference")
-
-def save(self, *args, **kwargs):
-
- # let the user specify silence as either a kwarg or an instance varible
- silent = self.silent
- if "silent" in kwargs:
- silent=silent or kwargs.pop("silent")
-
- caller_kind = "unknown"
-
- if ('synchronizer' in threading.current_thread().name):
- caller_kind = "synchronizer"
-
- if "caller_kind" in kwargs:
- caller_kind = kwargs.pop("caller_kind")
-
- always_update_timestamp = False
- if "always_update_timestamp" in kwargs:
- always_update_timestamp = always_update_timestamp or kwargs.pop("always_update_timestamp")
-
- # SMBAKER: if an object is trying to delete itself, or if the observer
- # is updating an object's backend_* fields, then let it slip past the
- # composite key check.
- ignore_composite_key_check=False
- if "update_fields" in kwargs:
- ignore_composite_key_check=True
- for field in kwargs["update_fields"]:
- if not (field in ["backend_register", "backend_status", "deleted", "enacted", "updated"]):
- ignore_composite_key_check=False
-
- if (caller_kind!="synchronizer") or always_update_timestamp:
- self.updated = timezone.now()
- else:
- # We're not auto-setting timestamp, but let's check to make sure that the caller hasn't tried to set our
- # timestamp backward...
- if (self.updated != self._initial["updated"]) and ((not kwargs.get("update_fields")) or ("updated" in kwargs.get("update_fields"))):
- log.info("Synchronizer tried to change `updated` timestamp on model %s from %s to %s. Ignored." % (self, self._initial["updated"], self.updated))
- self.updated = self._initial["updated"]
-
- with transaction.atomic():
- self.verify_live_keys(update_fields = kwargs.get("update_fields"))
- super(XOSBase, self).save(*args, **kwargs)
-
- self.push_redis_event()
-
- self._initial = self._dict
-
-def tologdict(self):
- try:
- d = {'model_name':self.__class__.__name__, 'pk': self.pk}
- except:
- d = {}
-
- return d
-
-# for the old django admin UI
-def __unicode__(self):
- if hasattr(self, "name") and self.name:
- return u'%s' % self.name
- elif hasattr(self, "id") and self.id:
- if hasattr(self, "leaf_model_name") and self.leaf_model_name:
- return u'%s-%s' % (self.leaf_model_name, self.id)
- else:
- return u'%s-%s' % (self.__class__.__name__, self.id)
- else:
- return u'%s-unsaved' % self.__class__.__name__
-
-def get_content_type_key(self):
- ct = ContentType.objects.get_for_model(self.__class__)
- return "%s.%s" % (ct.app_label, ct.model)
-
-@staticmethod
-def get_content_type_from_key(key):
- (app_name, model_name) = key.split(".")
- return ContentType.objects.get_by_natural_key(app_name, model_name)
-
-@staticmethod
-def get_content_object(content_type, object_id):
- ct = XOSBase.get_content_type_from_key(content_type)
- cls = ct.model_class()
- return cls.objects.get(id=object_id)
-
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controller.py
similarity index 81%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controller.py
index 4cf8491..e0d20e7 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controller.py
@@ -1,4 +1,3 @@
-
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,5 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controller_decl import *
-from service_header import *
+class Controller(Controller_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllerimages.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllerimages.py
index 4cf8491..fdec3d4 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllerimages.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controllerimages_decl import *
-from service_header import *
+class ControllerImages(ControllerImages_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/controllernetwork.py b/xos/core/models/controllernetwork.py
new file mode 100644
index 0000000..6259bdb
--- /dev/null
+++ b/xos/core/models/controllernetwork.py
@@ -0,0 +1,30 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from controllernetwork_decl import *
+
+class ControllerNetwork(ControllerNetwork_decl):
+ class Meta:
+ proxy = True
+
+ def tologdict(self):
+ d=super(ControllerNetwork,self).tologdict()
+ try:
+ d['network_name']=self.network.name
+ d['controller_name']=self.controller.name
+ except:
+ pass
+ return d
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllerrole.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllerrole.py
index 4cf8491..91746a1 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllerrole.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controllerrole_decl import *
-from service_header import *
+class ControllerRole(ControllerRole_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllersite.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllersite.py
index 4cf8491..5186c05 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllersite.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controllersite_decl import *
-from service_header import *
+class ControllerSite(ControllerSite_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllersiteprivilege.py
similarity index 77%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllersiteprivilege.py
index 4cf8491..915aa37 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllersiteprivilege.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controllersiteprivilege_decl import *
-from service_header import *
+class ControllerSitePrivilege(ControllerSitePrivilege_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/controllernetwork_model.py b/xos/core/models/controllerslice.py
similarity index 60%
rename from xos/core/models/attic/controllernetwork_model.py
rename to xos/core/models/controllerslice.py
index 1bd6a61..8b468af 100644
--- a/xos/core/models/attic/controllernetwork_model.py
+++ b/xos/core/models/controllerslice.py
@@ -13,12 +13,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controllerslice_decl import *
-def tologdict(self):
- d=super(ControllerNetwork,self).tologdict()
- try:
- d['network_name']=self.network.name
- d['controller_name']=self.controller.name
- except:
- pass
- return d
+class ControllerSlice(ControllerSlice_decl):
+ class Meta:
+ proxy = True
+
+ def tologdict(self):
+ d=super(ControllerSlice,self).tologdict()
+ try:
+ d['slice_name']=self.slice.name
+ d['controller_name']=self.controller.name
+ except:
+ pass
+ return d
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllersliceprivilege.py
similarity index 76%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllersliceprivilege.py
index 4cf8491..e9963ff 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllersliceprivilege.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controllersliceprivilege_decl import *
-from service_header import *
+class ControllerSlicePrivilege(ControllerSlicePrivilege_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controlleruser.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controlleruser.py
index 4cf8491..cdd152d 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controlleruser.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from controlleruser_decl import *
-from service_header import *
+class ControllerUser(ControllerUser_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 4cef3bd..f74b73d 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -1,10 +1,13 @@
option app_label = "core";
+option legacy="True";
// use thi policy to allow access to admins only
policy admin_policy < ctx.user.is_admin >
message XOSBase {
option skip_init = True;
+ option custom_header = "xosbase_header";
+ option abstract = True;
required string created = 1 [content_type = "date", auto_now_add = True];
required string updated = 2 [default = "now()", content_type = "date"];
@@ -189,24 +192,16 @@
optional string segmentation_id = 10 [db_index = False, max_length = 32, null = True, blank = True];
}
-
message ControllerRole (XOSBase) {
required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
}
-
message ControllerSite (XOSBase) {
required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False, unique_with="controller", tosca_key = True];
optional manytoone controller->Controller:controllersite = 2 [db_index = True, null = True, blank = True, tosca_key = True];
optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = True];
}
-message ControllerPrivilege (XOSBase) {
- required manytoone controller->Controller:controllerprivileges = 1 [db_index = True, null = False, blank = False];
- required manytoone privilege->Privilege:controllerprivileges = 2 [db_index = True, null = False, blank = False];
- optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
-}
-
message ControllerSitePrivilege (XOSBase) {
required manytoone controller->Controller:controllersiteprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "site_privilege"];
required manytoone site_privilege->SitePrivilege:controllersiteprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role_id"];
@@ -223,7 +218,6 @@
optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = False];
}
-
message ControllerSlicePrivilege (XOSBase) {
required manytoone controller->Controller:controllersliceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "slice_privilege"];
required manytoone slice_privilege->SlicePrivilege:controllersliceprivileges = 2 [db_index = True, null = False, blank = False];
@@ -252,18 +246,6 @@
}
-message DeploymentPrivilege (XOSBase) {
- required manytoone user->User:deploymentprivileges = 1 [db_index = True, null = False, blank = False, unique_with="deployment"];
- required manytoone deployment->Deployment:deploymentprivileges = 2 [db_index = True, null = False, blank = False, unique_with="role"];
- required manytoone role->DeploymentRole:deploymentprivileges = 3 [db_index = True, null = False, blank = False];
-}
-
-
-message DeploymentRole (XOSBase) {
- required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False, tosca_key=True];
-}
-
-
message Diag (XOSBase) {
option gui_hidden = True;
required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the synchronizer", null = False, db_index = False];
@@ -339,8 +321,6 @@
required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
- required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
- required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
}
@@ -453,19 +433,6 @@
}
-message ServicePrivilege (XOSBase) {
- required manytoone user->User:serviceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "service"];
- required manytoone service->Service:serviceprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
- required manytoone role->ServiceRole:serviceprivileges = 3 [db_index = True, null = False, blank = False];
-}
-
-
-message ServiceRole (XOSBase) {
- required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
-}
-
-
-
message Site::site_policy (XOSBase) {
required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name for this Site", null = False, db_index = False];
optional string site_url = 2 [max_length = 512, content_type = "url", blank = True, help_text = "Site's Home URL Page", null = True, db_index = False];
@@ -477,7 +444,6 @@
required string login_base = 8 [max_length = 50, content_type = "stripped", blank = False, help_text = "Prefix for Slices associated with this Site", null = False, db_index = False];
required bool is_public = 9 [help_text = "Indicates the visibility of this site to other members", default = True, null = False, db_index = False, blank = True];
required string abbreviated_name = 10 [db_index = False, max_length = 80, null = False, content_type = "stripped", blank = False];
- required manytomany deployments->Deployment/SiteDeployment:sites = 11 [help_text = "Select which sites are allowed to host nodes in this deployment", null = False, db_index = False, blank = True];
}
@@ -488,7 +454,6 @@
optional string availability_zone = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack availability zone", null = True, db_index = False];
}
-
message SitePrivilege (XOSBase) {
required manytoone user->User:siteprivileges = 1 [db_index = True, null = False, blank = False];
required manytoone site->Site:siteprivileges = 2 [db_index = True, null = False, blank = False, tosca_key=True];
@@ -527,7 +492,6 @@
required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
}
-
message SlicePrivilege (XOSBase) {
required manytoone user->User:sliceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
required manytoone slice->Slice:sliceprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
@@ -590,9 +554,9 @@
optional string node_label = 5 [max_length = 30, content_type = "stripped", blank = True, help_text = "Node constraint", null = True, db_index = False];
}
-message XOS (XOSBase) {
- option singular="XOS";
- option plural="XOSes";
+message XOSCore (XOSBase) {
+ option singular="XOSCore";
+ option plural="XOSCores";
required string name = 1 [default = "XOS", max_length = 200, content_type = "stripped", blank = False, help_text = "Name of XOS", null = False, db_index = False];
}
diff --git a/xos/core/models/deployment.py b/xos/core/models/deployment.py
new file mode 100644
index 0000000..ffc46e5
--- /dev/null
+++ b/xos/core/models/deployment.py
@@ -0,0 +1,54 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from deployment_decl import *
+from core.acl import AccessControlList
+
+class Deployment(Deployment_decl):
+ class Meta:
+ proxy = True
+
+ def get_acl(self):
+ return AccessControlList(self.accessControl)
+
+ def test_acl(self, slice=None, user=None):
+ potential_users=[]
+
+ if user:
+ potential_users.append(user)
+
+ if slice:
+ potential_users.append(slice.creator)
+ for priv in slice.sliceprivileges.all():
+ if priv.user not in potential_users:
+ potential_users.append(priv.user)
+
+ acl = self.get_acl()
+ for user in potential_users:
+ if acl.test(user) == "allow":
+ return True
+
+ return False
+
+ @staticmethod
+ def select_by_acl(user):
+ ids = []
+ for deployment in Deployment.objects.all():
+ acl = deployment.get_acl()
+ if acl.test(user) == "allow":
+ ids.append(deployment.id)
+
+ return Deployment.objects.filter(id__in=ids)
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/diag.py
similarity index 83%
rename from xos/core/models/attic/serviceinstance_top.py
rename to xos/core/models/diag.py
index 4cf8491..1af74ff 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/diag.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from diag_decl import *
-from service_header import *
+class Diag(Diag_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/flavor.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/flavor.py
index 4cf8491..e2156b2 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/flavor.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from flavor_decl import *
-from service_header import *
+class Flavor(Flavor_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/header.py b/xos/core/models/header.py
deleted file mode 120000
index 721b5c0..0000000
--- a/xos/core/models/header.py
+++ /dev/null
@@ -1 +0,0 @@
-attic/header.py
\ No newline at end of file
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/image.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/image.py
index 4cf8491..7aa139a 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/image.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from image_decl import *
-from service_header import *
+class Image(Image_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/imagedeployments.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/imagedeployments.py
index 4cf8491..3db6a47 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/imagedeployments.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from imagedeployments_decl import *
-from service_header import *
+class ImageDeployments(ImageDeployments_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
new file mode 100644
index 0000000..45215bc
--- /dev/null
+++ b/xos/core/models/instance.py
@@ -0,0 +1,108 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from instance_decl import *
+
+class Instance(Instance_decl):
+ class Meta:
+ proxy = True
+
+ def get_controller (self):
+ return self.node.site_deployment.controller
+
+ def tologdict(self):
+ d=super(Instance,self).tologdict()
+ try:
+ d['slice_name']=self.slice.name
+ d['controller_name']=self.get_controller().name
+ except:
+ pass
+ return d
+
+ def save(self, *args, **kwargs):
+ if not self.name:
+ self.name = self.slice.name
+ if not self.creator and hasattr(self, 'caller'):
+ self.creator = self.caller
+
+ super(Instance, self).save(*args, **kwargs)
+
+ def all_ips(self):
+ ips={}
+ for ns in self.ports.all():
+ if ns.ip:
+ ips[ns.network.name] = ns.ip
+ return ips
+
+ def all_ips_string(self):
+ result = []
+ ips = self.all_ips()
+ for key in sorted(ips.keys()):
+ #result.append("%s = %s" % (key, ips[key]))
+ result.append(ips[key])
+ return ", ".join(result)
+ all_ips_string.short_description = "addresses"
+
+ def get_public_ip(self):
+ for ns in self.ports.all():
+ if (ns.ip) and (ns.network.template.visibility=="public") and (ns.network.template.translation=="none"):
+ return ns.ip
+ return None
+
+ # return an address on nat-net
+ def get_network_ip(self, pattern):
+ for ns in self.ports.all():
+ if pattern in ns.network.name.lower():
+ return ns.ip
+ return None
+
+ # return an address that the synchronizer can use to SSH to the instance
+ def get_ssh_ip(self):
+ # first look specifically for a management_local network
+ for ns in self.ports.all():
+ if ns.network.template and ns.network.template.vtn_kind=="MANAGEMENT_LOCAL":
+ return ns.ip
+
+ # for compatibility, now look for any management network
+ management=self.get_network_ip("management")
+ if management:
+ return management
+
+ # if all else fails, look for nat-net (for OpenCloud?)
+ return self.get_network_ip("nat")
+
+ def get_ssh_command(self):
+ if (not self.instance_id) or (not self.node) or (not self.instance_name):
+ return None
+ else:
+ return 'ssh -o "ProxyCommand ssh -q %s@%s" ubuntu@%s' % (self.instance_id, self.node.name, self.instance_name)
+
+ def get_public_keys(self):
+ from core.models.sliceprivilege import Privilege
+ slice_privileges = Privilege.objects.filter(object_id=self.slice.id, object_type='Slice', accessor_type='User')
+ slice_users = [User.objects.get(pk = priv.accessor_id) for priv in slice_privileges]
+ pubkeys = set([u.public_key for u in slice_users if u.public_key])
+
+ if self.creator.public_key:
+ pubkeys.add(self.creator.public_key)
+
+ if self.slice.creator.public_key:
+ pubkeys.add(self.slice.creator.public_key)
+
+ if self.slice.service and self.slice.service.public_key:
+ pubkeys.add(self.slice.service.public_key)
+
+ return pubkeys
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/interfacetype.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/interfacetype.py
index 4cf8491..264a981 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/interfacetype.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from interfacetype_decl import *
-from service_header import *
+class InterfaceType(InterfaceType_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/network.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/network.py
index 4cf8491..466b30f 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/network.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from network_decl import *
-from service_header import *
+class Network(Network_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networkparameter.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networkparameter.py
index 4cf8491..b8d4b8c 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networkparameter.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from networkparameter_decl import *
-from service_header import *
+class NetworkParameter(NetworkParameter_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networkparametertype.py
similarity index 78%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networkparametertype.py
index 4cf8491..f33aa28 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networkparametertype.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from networkparametertype_decl import *
-from service_header import *
+class NetworkParameterType(NetworkParameterType_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networkslice.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networkslice.py
index 4cf8491..9a1b119 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networkslice.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from networkslice_decl import *
-from service_header import *
+class NetworkSlice(NetworkSlice_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networktemplate.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networktemplate.py
index 4cf8491..ee8f5bd 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networktemplate.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from networktemplate_decl import *
-from service_header import *
+class NetworkTemplate(NetworkTemplate_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/node.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/node.py
index 4cf8491..aea3bac 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/node.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from node_decl import *
-from service_header import *
+class Node(Node_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/nodelabel.py b/xos/core/models/nodelabel.py
new file mode 100644
index 0000000..68e5f5c
--- /dev/null
+++ b/xos/core/models/nodelabel.py
@@ -0,0 +1,38 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from nodelabel_decl import *
+
+class NodeLabel(NodeLabel_decl):
+ class Meta:
+ proxy = True
+
+ def save(self, *args, **kwargs):
+ """ Hack to allow the creation of NodeLabel objects from outside core
+ until the ORM is extended with support for ManyToMany relations.
+ """
+
+ if self.name and '###' in self.name:
+ from core.models import Node
+
+ self.name, node_id_str = self.name.split('###')
+ node_ids = map(int, node_id_str.split(','))
+
+ for node_id in node_ids:
+ node = Node.get(node_id)
+ self.node.add(node)
+
+ super(NodeLabel, self).save(*args, **kwargs)
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/port.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/port.py
index 4cf8491..bbee8a7 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/port.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from port_decl import *
-from service_header import *
+class Port(Port_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/privilege.py
similarity index 81%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/privilege.py
index 4cf8491..1485ec8 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/privilege.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from privilege_decl import *
-from service_header import *
+class Privilege(Privilege_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/role.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/role.py
index 4cf8491..447df8e 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/role.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from role_decl import *
-from service_header import *
+class Role(Role_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
new file mode 100644
index 0000000..b03ac70
--- /dev/null
+++ b/xos/core/models/service.py
@@ -0,0 +1,308 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from service_decl import *
+
+class Scheduler(object):
+ # XOS Scheduler Abstract Base Class
+ # Used to implement schedulers that pick which node to put instances on
+
+ def __init__(self, slice):
+ self.slice = slice
+
+ def pick(self):
+ # this method should return a tuple (node, parent)
+ # node is the node to instantiate on
+ # parent is for container_vm instances only, and is the VM that will
+ # hold the container
+
+ raise Exception("Abstract Base")
+
+
+class LeastLoadedNodeScheduler(Scheduler):
+ # This scheduler always return the node with the fewest number of
+ # instances.
+
+ def __init__(self, slice, label=None):
+ super(LeastLoadedNodeScheduler, self).__init__(slice)
+ self.label = label
+
+ def pick(self):
+ from core.models import Node
+
+ # start with all nodes
+ nodes = Node.objects.all()
+
+ # if a label is set, then filter by label
+ if self.label:
+ nodes = nodes.filter(nodelabels__name=self.label)
+
+ # if slice.default_node is set, then filter by default_node
+ if self.slice.default_node:
+ nodes = nodes.filter(name = self.slice.default_node)
+
+ # convert to list
+ nodes = list(nodes)
+
+ # sort so that we pick the least-loaded node
+ nodes = sorted(nodes, key=lambda node: node.instances.all().count())
+
+ if not nodes:
+ raise Exception(
+ "LeastLoadedNodeScheduler: No suitable nodes to pick from")
+
+ # TODO: logic to filter nodes by which nodes are up, and which
+ # nodes the slice can instantiate on.
+# nodes = sorted(nodes, key=lambda node: node.instances.all().count())
+ return [nodes[0], None]
+
+
+class ContainerVmScheduler(Scheduler):
+ # This scheduler picks a VM in the slice with the fewest containers inside
+ # of it. If no VMs are suitable, then it creates a VM.
+
+ MAX_VM_PER_CONTAINER = 10
+
+ def __init__(self, slice):
+ super(ContainerVmScheduler, self).__init__(slice)
+
+ @property
+ def image(self):
+ from core.models import Image
+
+ # If slice has default_image set then use it
+ if self.slice.default_image:
+ return self.slice.default_image
+
+ raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
+
+ def make_new_instance(self):
+ from core.models import Instance, Flavor
+
+ flavors = Flavor.objects.filter(name="m1.small")
+ if not flavors:
+ raise XOSConfigurationError("No m1.small flavor")
+
+ (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
+
+ instance = Instance(slice=self.slice,
+ node=node,
+ image=self.image,
+ creator=self.slice.creator,
+ deployment=node.site_deployment.deployment,
+ flavor=flavors[0],
+ isolation="vm",
+ parent=parent)
+ instance.save()
+ # We rely on a special naming convention to identify the VMs that will
+ # hole containers.
+ instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
+ instance.save()
+ return instance
+
+ def pick(self):
+ from core.models import Instance, Flavor
+
+ for vm in self.slice.instances.filter(isolation="vm"):
+ avail_vms = []
+ if (vm.name.startswith("%s-outer-" % self.slice.name)):
+ container_count = Instance.objects.filter(parent=vm).count()
+ if (container_count < self.MAX_VM_PER_CONTAINER):
+ avail_vms.append((vm, container_count))
+ # sort by least containers-per-vm
+ avail_vms = sorted(avail_vms, key=lambda x: x[1])
+ print "XXX", avail_vms
+ if avail_vms:
+ instance = avail_vms[0][0]
+ return (instance.node, instance)
+
+ instance = self.make_new_instance()
+ return (instance.node, instance)
+
+class Service(Service_decl):
+ class Meta:
+ proxy = True
+
+ KIND = "generic"
+
+ def __init__(self, *args, **kwargs):
+ # for subclasses, set the default kind appropriately
+ # TODO: rethink this -- remember the class variable bug
+ self._meta.get_field("kind").default = self.KIND
+ super(Service, self).__init__(*args, **kwargs)
+
+ @property
+ def serviceattribute_dict(self):
+ attrs = {}
+ for attr in self.serviceattributes.all():
+ attrs[attr.name] = attr.value
+ return attrs
+
+ def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
+ """
+ Get a list of nodes that can be used to scale up a slice.
+
+ slice - slice to scale up
+ max_per_node - maximum numbers of instances that 'slice' can have on a single node
+ exclusive_slices - list of slices that must have no nodes in common with 'slice'.
+ """
+
+ # late import to get around order-of-imports constraint in __init__.py
+ from core.models import Node, Instance
+
+ nodes = list(Node.objects.all())
+
+ conflicting_instances = Instance.objects.filter(
+ slice__in=exclusive_slices)
+ conflicting_nodes = Node.objects.filter(
+ instances__in=conflicting_instances)
+
+ nodes = [x for x in nodes if x not in conflicting_nodes]
+
+ # If max_per_node is set, then limit the number of instances this slice
+ # can have on a single node.
+ if max_per_node:
+ acceptable_nodes = []
+ for node in nodes:
+ existing_count = node.instances.filter(slice=slice).count()
+ if existing_count < max_per_node:
+ acceptable_nodes.append(node)
+ nodes = acceptable_nodes
+
+ return nodes
+
+ def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
+ # Pick the best node to scale up a slice.
+
+ nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
+ nodes = sorted(nodes, key=lambda node: node.instances.all().count())
+ if not nodes:
+ return None
+ return nodes[0]
+
+ def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
+ # late import to get around order-of-imports constraint in __init__.py
+ from core.models import Instance
+
+ slices = [x for x in self.slices.all() if slice_hint in x.name]
+ for slice in slices:
+ while slice.instances.all().count() > scale:
+ s = slice.instances.all()[0]
+ # print "drop instance", s
+ s.delete()
+
+ while slice.instances.all().count() < scale:
+ node = self.pick_node(slice, max_per_node, exclusive_slices)
+ if not node:
+ # no more available nodes
+ break
+
+ image = slice.default_image
+ if not image:
+ raise XOSConfigurationError(
+ "No default_image for slice %s" % slice.name)
+
+ flavor = slice.default_flavor
+ if not flavor:
+ raise XOSConfigurationError(
+ "No default_flavor for slice %s" % slice.name)
+
+ s = Instance(slice=slice,
+ node=node,
+ creator=slice.creator,
+ image=image,
+ flavor=flavor,
+ deployment=node.site_deployment.deployment)
+ s.save()
+
+ # print "add instance", s
+
+ def get_vtn_src_nets(self):
+ nets = []
+ for slice in self.slices.all():
+ for ns in slice.networkslices.all():
+ if not ns.network:
+ continue
+ # if ns.network.template.access in ["direct", "indirect"]:
+ # # skip access networks; we want to use the private network
+ # continue
+ if "management" in ns.network.name:
+ # don't try to connect the management network to anything
+ continue
+ if ns.network.name in ["wan_network", "lan_network"]:
+ # we don't want to attach to the vCPE's lan or wan network
+ # we only want to attach to its private network
+ # TODO: fix hard-coding of network name
+ continue
+ for cn in ns.network.controllernetworks.all():
+ if cn.net_id:
+ net = {"name": ns.network.name, "net_id": cn.net_id}
+ nets.append(net)
+ return nets
+
+ def get_vtn_nets(self):
+ nets = []
+ for slice in self.slices.all():
+ for ns in slice.networkslices.all():
+ if not ns.network:
+ continue
+ if ns.network.template.access not in ["direct", "indirect"]:
+ # skip anything that's not an access network
+ continue
+ for cn in ns.network.controllernetworks.all():
+ if cn.net_id:
+ net = {"name": ns.network.name, "net_id": cn.net_id}
+ nets.append(net)
+ return nets
+
+ def get_vtn_dependencies_nets(self):
+ provider_nets = []
+ for tenant in self.subscribed_tenants.all():
+ if tenant.provider_service:
+ for net in tenant.provider_service.get_vtn_nets():
+ if not net in provider_nets:
+ net["bidirectional"] = tenant.connect_method!="private-unidirectional"
+ provider_nets.append(net)
+ return provider_nets
+
+ def get_vtn_dependencies_ids(self):
+ return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
+
+ def get_vtn_dependencies_names(self):
+ return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
+
+ def get_vtn_src_ids(self):
+ return [x["net_id"] for x in self.get_vtn_src_nets()]
+
+ def get_vtn_src_names(self):
+ return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
+
+ def get_composable_networks(self):
+ SUPPORTED_VTN_SERVCOMP_KINDS = ['VSG','PRIVATE']
+
+ nets = []
+ for slice in self.slices.all():
+ for net in slice.networks.all():
+ if (net.template.vtn_kind not in SUPPORTED_VTN_SERVCOMP_KINDS) or (net.owner != slice):
+ continue
+
+ if not net.controllernetworks.exists():
+ continue
+ nets.append(net)
+ return nets
+
+
+
diff --git a/xos/core/models/service_header.py b/xos/core/models/service_header.py
deleted file mode 120000
index a90002d..0000000
--- a/xos/core/models/service_header.py
+++ /dev/null
@@ -1 +0,0 @@
-attic/service_header.py
\ No newline at end of file
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/serviceattribute.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/serviceattribute.py
index 4cf8491..19b4550 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/serviceattribute.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from serviceattribute_decl import *
-from service_header import *
+class ServiceAttribute(ServiceAttribute_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/servicedependency.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/servicedependency.py
index 4cf8491..ae661b3 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/servicedependency.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from servicedependency_decl import *
-from service_header import *
+class ServiceDependency(ServiceDependency_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/servicegraphconstraint.py
similarity index 77%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/servicegraphconstraint.py
index 4cf8491..cc703a8 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/servicegraphconstraint.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from servicegraphconstraint_decl import *
-from service_header import *
+class ServiceGraphConstraint(ServiceGraphConstraint_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/serviceinstance.py b/xos/core/models/serviceinstance.py
new file mode 100644
index 0000000..9cc6dab
--- /dev/null
+++ b/xos/core/models/serviceinstance.py
@@ -0,0 +1,65 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from serviceinstance_decl import *
+
+class ServiceInstance(ServiceInstance_decl):
+ class Meta:
+ proxy = True
+
+ def __init__(self, *args, **kwargs):
+ super(ServiceInstance, self).__init__(*args, **kwargs)
+
+ @property
+ def tenantattribute_dict(self):
+ attrs = {}
+ for attr in self.tenantattributes.all():
+ attrs[attr.name] = attr.value
+ return attrs
+
+ # helper function to be used in subclasses that want to ensure
+ # service_specific_id is unique
+
+ def validate_unique_service_specific_id(self, none_okay=False):
+ if not none_okay and (self.service_specific_id is None):
+ raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
+ "service_specific_id": "cannot be none"})
+
+ if self.service_specific_id:
+ conflicts = self.__class__.objects.filter(
+ service_specific_id=self.service_specific_id)
+ if self.pk:
+ conflicts = conflicts.exclude(pk=self.pk)
+ if conflicts:
+ raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
+ "service_specific_id": "duplicate key"})
+
+ def get_subscribed_tenants(self, tenant_class):
+ """ Return all ServiceInstances of class tenant_class that have a link to this ServiceInstance """
+ results=[]
+ # TODO: Make query more efficient
+ for si in tenant_class.objects.all():
+ for link in si.subscribed_links.all():
+ if link.provider_service_instance == self:
+ results.append(si)
+ return results
+
+ def get_newest_subscribed_tenant(self, kind):
+ st = list(self.get_subscribed_tenants(kind))
+ if not st:
+ return None
+ return sorted(st, key=attrgetter('id'))[0]
+
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/serviceinstanceattribute.py
similarity index 76%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/serviceinstanceattribute.py
index 4cf8491..8dcaa81 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/serviceinstanceattribute.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from serviceinstanceattribute_decl import *
-from service_header import *
+class ServiceInstanceAttribute(ServiceInstanceAttribute_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/serviceinstancelink.py b/xos/core/models/serviceinstancelink.py
new file mode 100644
index 0000000..c2ce0c1
--- /dev/null
+++ b/xos/core/models/serviceinstancelink.py
@@ -0,0 +1,57 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from serviceinstancelink_decl import *
+
+class ServiceInstanceLink(ServiceInstanceLink_decl):
+ class Meta:
+ proxy = True
+
+ def save(self, *args, **kwargs):
+ subCount = sum([1 for e in [self.subscriber_service, self.subscriber_service_instance, self.subscriber_network] if e is not None])
+ if (subCount > 1):
+ raise XOSConflictingField(
+ "Only one of subscriber_service, subscriber_service_instance, subscriber_network should be set")
+
+ try:
+ existing_instance = ServiceInstanceLink.objects.get(
+ provider_service_instance=self.provider_service_instance,
+ subscriber_service_instance=self.subscriber_service_instance,
+ subscriber_service=self.subscriber_service,
+ subscriber_network=self.subscriber_network
+ )
+
+ if (not self.pk and existing_instance) or (self.pk and self.pk != existing_instance.pk):
+ raise XOSValidationError(
+ "A ServiceInstanceLink with attributes 'provider_service_instance=%s, subscriber_service_instance=%s, subscriber_service=%s, subscriber_network=%s' already exists"
+ % (self.provider_service_instance, self.subscriber_service_instance, self.subscriber_service,
+ self.subscriber_network))
+ except self.DoesNotExist:
+ # NOTE this is correct, no duplicated links
+ pass
+
+ super(ServiceInstanceLink, self).save(*args, **kwargs)
+
+
+ def delete(self, *args, **kwargs):
+ provider_service_instance = self.provider_service_instance
+ super(ServiceInstanceLink, self).delete(*args, **kwargs)
+
+ # This should be handled by a model_policy, but we don't currently have a
+ # model policy for core objects, so handle it during the save method.
+ if provider_service_instance and (not provider_service_instance.deleted):
+ provider_service_instance.link_deleted_count += 1
+ provider_service_instance.save(always_update_timestamp=True, update_fields=["updated", "link_deleted_count"])
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/serviceinterface.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/serviceinterface.py
index 4cf8491..c6a7c5d 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/serviceinterface.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from serviceinterface_decl import *
-from service_header import *
+class ServiceInterface(ServiceInterface_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/servicemonitoringagentinfo.py
similarity index 76%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/servicemonitoringagentinfo.py
index 4cf8491..7b7befc 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/servicemonitoringagentinfo.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from servicemonitoringagentinfo_decl import *
-from service_header import *
+class ServiceMonitoringAgentInfo(ServiceMonitoringAgentInfo_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/site.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/site.py
index 4cf8491..1fabf93 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/site.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from site_decl import *
-from service_header import *
+class Site(Site_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/sitedeployment.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/sitedeployment.py
index 4cf8491..9c79264 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/sitedeployment.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from sitedeployment_decl import *
-from service_header import *
+class SiteDeployment(SiteDeployment_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/siteprivilege.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/siteprivilege.py
index 4cf8491..4c13592 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/siteprivilege.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from siteprivilege_decl import *
-from service_header import *
+class SitePrivilege(SitePrivilege_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/siterole.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/siterole.py
index 4cf8491..0c89ef3 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/siterole.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from siterole_decl import *
-from service_header import *
+class SiteRole(SiteRole_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/slice.py b/xos/core/models/slice.py
new file mode 100644
index 0000000..3cf55f4
--- /dev/null
+++ b/xos/core/models/slice.py
@@ -0,0 +1,51 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from slice_decl import *
+
+class Slice(Slice_decl):
+ class Meta:
+ proxy = True
+
+ NETWORK_CHOICES = ((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))
+
+ @property
+ def slicename(self):
+ return "%s_%s" % (self.site.login_base, self.name)
+
+ def save(self, *args, **kwargs):
+ # set creator on first save
+ if not self.creator and hasattr(self, 'caller'):
+ self.creator = self.caller
+
+ # only admins change a slice's creator
+ if 'creator' in self.changed_fields and \
+ (not hasattr(self, 'caller') or not self.caller.is_admin):
+
+ if (self._initial["creator"]==None) and (self.creator==getattr(self,"caller",None)):
+ # it's okay if the creator is being set by the caller to
+ # himeself on a new slice object.
+ pass
+ else:
+ raise PermissionDenied("Insufficient privileges to change slice creator",
+ {'creator': "Insufficient privileges to change slice creator"})
+
+ if self.network=="Private Only":
+ # "Private Only" was the default from the old Tenant View
+ self.network=None
+ self.enforce_choices(self.network, self.NETWORK_CHOICES)
+
+ super(Slice, self).save(*args, **kwargs)
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/sliceprivilege.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/sliceprivilege.py
index 4cf8491..53eab21 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/sliceprivilege.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from sliceprivilege_decl import *
-from service_header import *
+class SlicePrivilege(SlicePrivilege_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/slicerole.py
similarity index 81%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/slicerole.py
index 4cf8491..373fb8c 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/slicerole.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from slicerole_decl import *
-from service_header import *
+class SliceRole(SliceRole_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/tag.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/tag.py
index 4cf8491..ae3a8f1 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/tag.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from tag_decl import *
-from service_header import *
+class Tag(Tag_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/tenantwithcontainer.py b/xos/core/models/tenantwithcontainer.py
new file mode 100644
index 0000000..f6b73b4
--- /dev/null
+++ b/xos/core/models/tenantwithcontainer.py
@@ -0,0 +1,164 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from tenantwithcontainer_decl import *
+
+class TenantWithContainer(TenantWithContainer_decl):
+ class Meta:
+ proxy = True
+
+ def __init__(self, *args, **kwargs):
+ super(TenantWithContainer, self).__init__(*args, **kwargs)
+
+ # vSG service relies on knowing when instance id has changed
+ self.orig_instance_id = self.get_attribute("instance_id")
+
+ # vSG service relies on instance_id attribute
+ def get_attribute(self, name, default=None):
+ if name=="instance_id":
+ if self.instance:
+ return self.instance.id
+ else:
+ return None
+ else:
+ return super(TenantWithContainer, self).get_attribute(name, default)
+
+ # Services may wish to override the image() function to return different
+ # images based on criteria in the tenant object. For example,
+ # if (self.has_feature_A):
+ # return Instance.object.get(name="image_with_feature_a")
+ # elif (self.has_feature_B):
+ # return Instance.object.get(name="image_with_feature_b")
+ # else:
+ # return super(MyTenantClass,self).image()
+
+ @property
+ def image(self):
+ from core.models import Image
+ # Implement the logic here to pick the image that should be used when
+ # instantiating the VM that will hold the container.
+
+ slice = self.provider_service.slices.all()
+ if not slice:
+ raise XOSProgrammingError("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)
+
+ def save_instance(self, instance):
+ # Override this function to do custom pre-save or post-save processing,
+ # such as creating ports for containers.
+ instance.save()
+
+ def pick_least_loaded_instance_in_slice(self, slices, image):
+ for slice in slices:
+ if slice.instances.all().count() > 0:
+ for instance in slice.instances.all():
+ if instance.image != image:
+ continue
+ # Pick the first instance that has lesser than 5 tenants
+ if self.count_of_tenants_of_an_instance(instance) < 5:
+ return instance
+ return None
+
+ # TODO: Ideally the tenant count for an instance should be maintained using a
+ # many-to-one relationship attribute, however this model being proxy, it does
+ # not permit any new attributes to be defined. Find if any better solutions
+ def count_of_tenants_of_an_instance(self, instance):
+ tenant_count = 0
+ for tenant in self.__class__.objects.all():
+ if tenant.get_attribute("instance_id", None) == instance.id:
+ tenant_count += 1
+ return tenant_count
+
+ def manage_container(self):
+ from core.models import Instance, Flavor
+
+ if self.deleted:
+ return
+
+ if (self.instance is not None) and (self.instance.image != self.image):
+ self.instance.delete()
+ self.instance = None
+
+ if self.instance is None:
+ if not self.provider_service.slices.count():
+ raise XOSConfigurationError("The service has no slices")
+
+ new_instance_created = False
+ instance = None
+ if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
+ # Find if any existing instances can be used for this tenant
+ slices = self.provider_service.slices.all()
+ instance = self.pick_least_loaded_instance_in_slice(slices, self.image)
+
+ if not instance:
+ slice = self.provider_service.slices.all()[0]
+
+ flavor = slice.default_flavor
+ if not flavor:
+ flavors = Flavor.objects.filter(name="m1.small")
+ if not flavors:
+ raise XOSConfigurationError("No m1.small flavor")
+ flavor = flavors[0]
+
+ if slice.default_isolation == "container_vm":
+ (node, parent) = ContainerVmScheduler(slice).pick()
+ else:
+ (node, parent) = LeastLoadedNodeScheduler(slice).pick()
+
+ instance = Instance(slice=slice,
+ node=node,
+ image=self.image,
+ creator=self.creator,
+ deployment=node.site_deployment.deployment,
+ flavor=flavor,
+ isolation=slice.default_isolation,
+ parent=parent)
+ self.save_instance(instance)
+ new_instance_created = True
+
+ try:
+ self.instance = instance
+ super(TenantWithContainer, self).save()
+ except:
+ if new_instance_created:
+ instance.delete()
+ raise
+
+ def cleanup_container(self):
+ if self.instance:
+ if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
+ # Delete the instance only if this is last tenant in that
+ # instance
+ tenant_count = self.count_of_tenants_of_an_instance(
+ self.instance)
+ if tenant_count == 0:
+ self.instance.delete()
+ else:
+ self.instance.delete()
+ self.instance = None
+
+ def save(self, *args, **kwargs):
+ if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
+ self.creator = self.caller
+
+ super(TenantWithContainer, self).save(*args, **kwargs)
+
diff --git a/xos/core/models/xosbase.py b/xos/core/models/xosbase.py
new file mode 100644
index 0000000..03404cf
--- /dev/null
+++ b/xos/core/models/xosbase.py
@@ -0,0 +1,196 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xos.exceptions import *
+from xosbase_decl import *
+
+class XOSBase(XOSBase_decl):
+ objects = XOSBaseManager()
+ deleted_objects = XOSBaseDeletionManager()
+
+ class Meta:
+ # Changing abstract to False would require the managers of subclasses of
+ # XOSBase to be customized individually.
+ abstract = True
+ app_label = "core"
+
+ def __init__(self, *args, **kwargs):
+ super(XOSBase, self).__init__(*args, **kwargs)
+ self._initial = self._dict # for PlModelMixIn
+ self.silent = False
+
+ def get_controller(self):
+ return self.controller
+
+ def delete(self, *args, **kwds):
+ # so we have something to give the observer
+ purge = kwds.get('purge',False)
+ if purge:
+ del kwds['purge']
+ silent = kwds.get('silent',False)
+ if silent:
+ del kwds['silent']
+ try:
+ purge = purge or observer_disabled
+ except NameError:
+ pass
+
+ if (purge):
+ pk = self.pk
+ super(XOSBase, self).delete(*args, **kwds)
+ self.push_redis_event(deleted=True, pk=pk)
+ else:
+ if (not self.write_protect ):
+ self.deleted = True
+ self.enacted=None
+ self.policed=None
+ self.save(update_fields=['enacted','deleted','policed'], silent=silent)
+
+ collector = XOSCollector(using=router.db_for_write(self.__class__, instance=self))
+ collector.collect([self])
+ with transaction.atomic():
+ for (k, models) in collector.data.items():
+ for model in models:
+ if not hasattr(model, "deleted"):
+ # Automatically generated through relations from ManyToMany fields do not have soft-delete
+ # capability.
+ continue
+ if model.deleted:
+ # in case it's already been deleted, don't delete again
+ continue
+ model.deleted = True
+ model.enacted=None
+ model.policed=None
+ model.save(update_fields=['enacted','deleted','policed'], silent=silent)
+
+ def verify_live_keys(self, update_fields):
+ """ Check the fields to be updated, if they contain foreign keys, that the foreign keys only point
+ to live objects in the database.
+
+ This is to catch races between model policies where an object is being deleted while a model policy is
+ still operating on it.
+ """
+
+ if getattr(self, "deleted", False):
+ # If this model is already deleted, then no need to check anything. We only need to check for live
+ # models that point to dead models. If a dead model points to other dead models, then we could
+ # be updating something else in the dead model (backend_status, etc)
+ return
+
+ for field in self._meta.fields:
+ try:
+ f = getattr(self, field.name)
+ except Exception, e:
+ # Exception django.db.models.fields.related.RelatedObjectDoesNotExist
+ # is thrown by django when you're creating an object that has a base and the base doesn't exist yet
+ continue
+
+ if f is None:
+ # If field hold a null value, we don't care
+ continue
+
+ ftype = field.get_internal_type()
+ if (ftype != "ForeignKey"):
+ # If field isn't a foreign key, we don't care
+ continue
+
+ if (update_fields) and (field.name not in update_fields):
+ # If update_fields is nonempty, and field is not to be updated, we don't care.
+ continue
+
+ if getattr(f, "deleted", False):
+ raise Exception("Attempt to save object with deleted foreign key reference")
+
+ def save(self, *args, **kwargs):
+
+ # let the user specify silence as either a kwarg or an instance varible
+ silent = self.silent
+ if "silent" in kwargs:
+ silent=silent or kwargs.pop("silent")
+
+ caller_kind = "unknown"
+
+ if ('synchronizer' in threading.current_thread().name):
+ caller_kind = "synchronizer"
+
+ if "caller_kind" in kwargs:
+ caller_kind = kwargs.pop("caller_kind")
+
+ always_update_timestamp = False
+ if "always_update_timestamp" in kwargs:
+ always_update_timestamp = always_update_timestamp or kwargs.pop("always_update_timestamp")
+
+ # SMBAKER: if an object is trying to delete itself, or if the observer
+ # is updating an object's backend_* fields, then let it slip past the
+ # composite key check.
+ ignore_composite_key_check=False
+ if "update_fields" in kwargs:
+ ignore_composite_key_check=True
+ for field in kwargs["update_fields"]:
+ if not (field in ["backend_register", "backend_status", "deleted", "enacted", "updated"]):
+ ignore_composite_key_check=False
+
+ if (caller_kind!="synchronizer") or always_update_timestamp:
+ self.updated = timezone.now()
+ else:
+ # We're not auto-setting timestamp, but let's check to make sure that the caller hasn't tried to set our
+ # timestamp backward...
+ if (self.updated != self._initial["updated"]) and ((not kwargs.get("update_fields")) or ("updated" in kwargs.get("update_fields"))):
+ log.info("Synchronizer tried to change `updated` timestamp on model %s from %s to %s. Ignored." % (self, self._initial["updated"], self.updated))
+ self.updated = self._initial["updated"]
+
+ with transaction.atomic():
+ self.verify_live_keys(update_fields = kwargs.get("update_fields"))
+ super(XOSBase, self).save(*args, **kwargs)
+
+ self.push_redis_event()
+
+ self._initial = self._dict
+
+ def tologdict(self):
+ try:
+ d = {'model_name':self.__class__.__name__, 'pk': self.pk}
+ except:
+ d = {}
+
+ return d
+
+ # for the old django admin UI
+ def __unicode__(self):
+ if hasattr(self, "name") and self.name:
+ return u'%s' % self.name
+ elif hasattr(self, "id") and self.id:
+ if hasattr(self, "leaf_model_name") and self.leaf_model_name:
+ return u'%s-%s' % (self.leaf_model_name, self.id)
+ else:
+ return u'%s-%s' % (self.__class__.__name__, self.id)
+ else:
+ return u'%s-unsaved' % self.__class__.__name__
+
+ def get_content_type_key(self):
+ ct = ContentType.objects.get_for_model(self.__class__)
+ return "%s.%s" % (ct.app_label, ct.model)
+
+ @staticmethod
+ def get_content_type_from_key(key):
+ (app_name, model_name) = key.split(".")
+ return ContentType.objects.get_by_natural_key(app_name, model_name)
+
+ @staticmethod
+ def get_content_object(content_type, object_id):
+ ct = XOSBase.get_content_type_from_key(content_type)
+ cls = ct.model_class()
+ return cls.objects.get(id=object_id)
+
diff --git a/xos/core/models/xosbase_header.py b/xos/core/models/xosbase_header.py
deleted file mode 120000
index dd654e6..0000000
--- a/xos/core/models/xosbase_header.py
+++ /dev/null
@@ -1 +0,0 @@
-attic/xosbase_header.py
\ No newline at end of file
diff --git a/xos/core/models/attic/xosbase_header.py b/xos/core/models/xosbase_header.py
similarity index 79%
rename from xos/core/models/attic/xosbase_header.py
rename to xos/core/models/xosbase_header.py
index 8afc3f0..f16b611 100644
--- a/xos/core/models/attic/xosbase_header.py
+++ b/xos/core/models/xosbase_header.py
@@ -22,6 +22,7 @@
import inspect
import sys
import threading
+import django
from django.db import models
from django.utils.timezone import now
from django.db.models import *
@@ -31,7 +32,6 @@
from django.core.exceptions import PermissionDenied
from cgi import escape as html_escape
from django.db.models.deletion import Collector
-from django.db.models.query import QuerySet
from django.db import router
from django.contrib.contenttypes.models import ContentType
@@ -144,6 +144,29 @@
def get_field_diff(self, field_name):
return self.diff.get(field_name, None)
+ @property
+ def leaf_model(self):
+ leaf_model_name = getattr(self, "leaf_model_name", None)
+ if not leaf_model_name:
+ return self
+
+ if (leaf_model_name == self.__class__.__name__):
+ return self
+
+ all_models = django.apps.apps.get_models(include_auto_created=False)
+ all_models_by_name = {}
+ for model in all_models:
+ all_models_by_name[model.__name__] = model
+
+ leaf_model_class = all_models_by_name.get(self.leaf_model_name)
+
+ assert (self.id)
+
+ if self.deleted:
+ return leaf_model_class.deleted_objects.get(id=self.id)
+ else:
+ return leaf_model_class.objects.get(id=self.id)
+
#classmethod
def getValidators(cls):
""" primarily for REST API, return a dictionary of field names mapped
@@ -285,6 +308,54 @@
log.error('Connection to Redis failed')
pass
+class AttributeMixin(object):
+ # helper for extracting things from a json-encoded
+ # service_specific_attribute
+
+ def get_attribute(self, name, default=None):
+ if self.service_specific_attribute:
+ attributes = json.loads(self.service_specific_attribute)
+ else:
+ 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)
+
+ def get_initial_attribute(self, name, default=None):
+ if self._initial["service_specific_attribute"]:
+ attributes = json.loads(
+ self._initial["service_specific_attribute"])
+ else:
+ attributes = {}
+ return attributes.get(name, default)
+
+ @classmethod
+ def get_default_attribute(cls, name):
+ for (attrname, default) in cls.simple_attributes:
+ if attrname == name:
+ return default
+ if hasattr(cls, "default_attributes"):
+ if name in cls.default_attributes:
+ return cls.default_attributes[name]
+
+ return None
+
+ @classmethod
+ def setup_simple_attributes(cls):
+ for (attrname, default) in cls.simple_attributes:
+ setattr(cls, attrname, property(
+ lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
+ lambda self, value, attrname=attrname: self.set_attribute(
+ attrname, value),
+ None,
+ attrname))
+
# For cascading deletes, we need a Collector that doesn't do fastdelete,
# so we get a full list of models.
class XOSCollector(Collector):
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/xoscore.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/xoscore.py
index 4cf8491..1243a56 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/xoscore.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from xoscore_decl import *
-from service_header import *
+class XOSCore(XOSCore_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/xosguiextension.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/xosguiextension.py
index 4cf8491..835a430 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/xosguiextension.py
@@ -13,5 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from xos.exceptions import *
+from xosguiextension_decl import *
-from service_header import *
+class XOSGuiExtension(XOSGuiExtension_decl):
+ class Meta:
+ proxy = True
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index 7c8c6a0..3bb2abb 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -239,7 +239,6 @@
elif (ftype == "GenericIPAddressField"):
setattr(p_obj, field.name, str(getattr(obj, field.name)))
-
# Introspecting the django object for related objects is problematic due to _decl-style attics. The descendant
# class's _meta's related_objects doesn't include related objects from the base. For example, VSGServiceInstance
# was missing provided_links and subscribed_links, since those were declared in ServiceInstance. (This problem
diff --git a/xos/coreapi/reaper.py b/xos/coreapi/reaper.py
index 75f75b2..ebc4556 100644
--- a/xos/coreapi/reaper.py
+++ b/xos/coreapi/reaper.py
@@ -138,6 +138,9 @@
log.info("skipping because it has need_delete_policy set", object = d)
continue
+ if hasattr(d, "leaf_model"):
+ d = d.leaf_model
+
cascade_set = self.get_cascade_set(d)
if cascade_set:
self.journal_object(d, "reaper.cascade_set", msg=",".join([str(m) for m in cascade_set]))
@@ -152,6 +155,7 @@
self.journal_object(d, "reaper.purge.exception")
log.error('REAPER: exception purging object', object = d)
traceback.print_exc()
+
try:
reset_queries()
except:
diff --git a/xos/xos_client/xosapi/convenience/network.py b/xos/xos_client/xosapi/convenience/network.py
new file mode 100644
index 0000000..7ea792a
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/network.py
@@ -0,0 +1,33 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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 json
+from xosapi.orm import ORMWrapper, ORMLocalObjectManager, register_convenience_wrapper
+
+class ORMWrapperNetwork(ORMWrapper):
+ # slices- emulates the ManyToMany from Slice to Network via NetworkSlice
+ @property
+ def slices(self):
+ idList = [x.slice.id for x in self.networkslices.all()]
+ return ORMLocalObjectManager(self.stub, "Slice", idList, False)
+
+ # instances- emulates the ManyToMany from Network to Instance via Port
+ @property
+ def instances(self):
+ idList = [x.instance.id for x in self.links.all()]
+ return ORMLocalObjectManager(self.stub, "Instance", idList, False)
+
+register_convenience_wrapper("Network", ORMWrapperNetwork)
diff --git a/xos/xos_client/xosapi/convenience/slice.py b/xos/xos_client/xosapi/convenience/slice.py
index 7686129..8431bd2 100644
--- a/xos/xos_client/xosapi/convenience/slice.py
+++ b/xos/xos_client/xosapi/convenience/slice.py
@@ -15,7 +15,7 @@
import json
-from xosapi.orm import ORMWrapper, register_convenience_wrapper
+from xosapi.orm import ORMWrapper, ORMLocalObjectManager, register_convenience_wrapper
class ORMWrapperSlice(ORMWrapper):
# TODO: this looks to be incorrect
@@ -23,4 +23,10 @@
def slicename(self):
return "%s_%s" % (self.site.login_base, self.name)
+ # networks - emulates the ManyToMany from Slice to Network via NetworkSlice
+ @property
+ def networks(self):
+ idList = [x.network.id for x in self.networkslices.all()]
+ return ORMLocalObjectManager(self.stub, "Network", idList, False)
+
register_convenience_wrapper("Slice", ORMWrapperSlice)
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index adbe58d..db9c468 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -610,6 +610,7 @@
import convenience.addresspool
import convenience.privilege
import convenience.instance
+import convenience.network
import convenience.cordsubscriberroot
import convenience.volttenant
import convenience.vsgserviceinstance