Merge branch 'acb-develop'
Activate enabling / disabling service
diff --git a/xos/configurations/acord/Makefile b/xos/configurations/acord/Makefile
index 5231e79..5b03692 100644
--- a/xos/configurations/acord/Makefile
+++ b/xos/configurations/acord/Makefile
@@ -1,16 +1,18 @@
SETUPDIR:=../setup
MYIP:=$(shell hostname -i)
-cloudlab: common_cloudlab acord
+cloudlab: common_cloudlab cloudlab_ceilometer_custom_images acord
-devstack: upgrade_pkgs common_devstack devstack_net_fix acord
+devstack: upgrade_pkgs common_devstack devstack_net_fix devstack_images acord
-acord: ceilometer_dashboard
+cord: ceilometer_dashboard
sudo MYIP=$(MYIP) docker-compose up -d
bash ../common/wait_for_xos.sh
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/fixtures.yaml
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/base.yaml
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
+
+acord: cord
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/acord/ceilometer.yaml
containers:
@@ -57,3 +59,9 @@
rebuild_synchronizer:
make -C ../../../containers/synchronizer
+
+devstack_images:
+ bash -c "source ../setup/admin-openrc.sh; glance image-show ceilometer-trusty-server-multi-nic || ! mkdir -p /opt/stack/images || ! wget http://www.vicci.org/cord/ceilometer-trusty-server-multi-nic.qcow2 -P /opt/stack/images || glance image-create --name ceilometer-trusty-server-multi-nic --disk-format qcow2 --file /opt/stack/images/ceilometer-trusty-server-multi-nic.qcow2 --container-format bare"
+
+cloudlab_ceilometer_custom_images:
+ bash -c "source ../setup/admin-openrc.sh; glance image-show ceilometer-trusty-server-multi-nic || ! mkdir -p /tmp/images || ! wget http://www.vicci.org/cord/ceilometer-trusty-server-multi-nic.qcow2 -P /tmp/images || glance image-create --name ceilometer-trusty-server-multi-nic --disk-format qcow2 --file /tmp/images/ceilometer-trusty-server-multi-nic.qcow2 --container-format bare"
diff --git a/xos/configurations/acord/ceilometer.yaml b/xos/configurations/acord/ceilometer.yaml
index 874c577..ff56579 100644
--- a/xos/configurations/acord/ceilometer.yaml
+++ b/xos/configurations/acord/ceilometer.yaml
@@ -123,9 +123,9 @@
view_url: /admin/ceilometer/ceilometerservice/$id$/
kind: ceilometer
ceilometer_pub_sub_url: http://10.11.10.1:4455/
-# public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
-# artifacts:
-# pubkey: /opt/xos/synchronizers/vcpe/vcpe_public_key
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ artifacts:
+ pubkey: /opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key
# service_sflow:
# type: tosca.nodes.SFlowService
@@ -161,6 +161,9 @@
trusty-server-multi-nic:
type: tosca.nodes.Image
+ ceilometer-trusty-server-multi-nic:
+ type: tosca.nodes.Image
+
mysite_ceilometer:
description: Ceilometer Proxy Slice
type: tosca.nodes.Slice
@@ -172,10 +175,10 @@
node: mysite
relationship: tosca.relationships.MemberOfSite
- default_image:
- node: trusty-server-multi-nic
+ node: ceilometer-trusty-server-multi-nic
relationship: tosca.relationships.DefaultImage
properties:
- default_flavor: m1.medium
+ default_flavor: m1.small
max_instances: 2
# mysite_sflow:
diff --git a/xos/configurations/acord/cleanup.sh b/xos/configurations/acord/cleanup.sh
index 91d821c..dfa1438 100755
--- a/xos/configurations/acord/cleanup.sh
+++ b/xos/configurations/acord/cleanup.sh
@@ -13,7 +13,7 @@
neutron net-delete $NETWORK
}
-source ../../setup/admin-openrc.sh
+source ../setup/admin-openrc.sh
echo "Deleting VMs"
# Delete all VMs
diff --git a/xos/configurations/common/devstack/local.conf b/xos/configurations/common/devstack/local.conf
index 38cd20c..632ed1b 100644
--- a/xos/configurations/common/devstack/local.conf
+++ b/xos/configurations/common/devstack/local.conf
@@ -2,7 +2,8 @@
[[local|localrc]]
DOWNLOAD_DEFAULT_IMAGES=false
-IMAGE_URLS="http://www.planet-lab.org/cord/trusty-server-multi-nic.img"
+IMAGE_URLS="http://www.planet-lab.org/cord/trusty-server-multi-nic.img,"
+IMAGE_URLS+="http://www.vicci.org/cord/ceilometer-trusty-server-multi-nic.qcow2"
LIBVIRT_FIREWALL_DRIVER=nova.virt.firewall.NoopFirewallDriver
# Append the git branch name if you wish to download ceilometer from a specific branch
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
diff --git a/xos/configurations/cord-pod/cord-vtn-vsg.yaml b/xos/configurations/cord-pod/cord-vtn-vsg.yaml
index 89ef49a..c06e237 100644
--- a/xos/configurations/cord-pod/cord-vtn-vsg.yaml
+++ b/xos/configurations/cord-pod/cord-vtn-vsg.yaml
@@ -37,6 +37,7 @@
wan_container_gateway_ip: 10.168.0.1
wan_container_gateway_mac: 02:42:0a:a8:00:01
wan_container_netbits: 24
+# node_label: label_vsg
artifacts:
pubkey: /opt/xos/synchronizers/vcpe/vcpe_public_key
@@ -61,6 +62,9 @@
mysite:
type: tosca.nodes.Site
+ label_vsg:
+ type: tosca.nodes.NodeLabel
+
# Networks required by the CORD setup
mysite_vsg-access:
type: tosca.nodes.network.Network
diff --git a/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh b/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh
index 26e4066..a1e5137 100755
--- a/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh
+++ b/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh
@@ -69,8 +69,8 @@
]
}
},
- "org.onosproject.openstackswitching" : {
- "openstackswitching" : {
+ "org.onosproject.openstackinterface" : {
+ "openstackinterface" : {
"do_not_push_flows" : "true",
"neutron_server" : "$NEUTRON_URL/v2.0/",
"keystone_server" : "$OS_AUTH_URL/",
diff --git a/xos/configurations/cord/Makefile b/xos/configurations/cord/Makefile
index 41bb37f..56990a2 100644
--- a/xos/configurations/cord/Makefile
+++ b/xos/configurations/cord/Makefile
@@ -1,7 +1,7 @@
SETUPDIR:=../setup
MYIP:=$(shell hostname -i)
-cloudlab: common_cloudlab cord
+cloudlab: common_cloudlab cord acord
devstack: upgrade_pkgs common_devstack devstack_net_fix cord
@@ -12,7 +12,6 @@
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/base.yaml
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord/cord.yaml
- sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord/ceilometer.yaml
containers:
cd ../../../containers/xos; make devel
@@ -24,6 +23,9 @@
common_devstack:
make -C ../common -f Makefile.devstack
+acord: cloudlab_ceilometer_custom_images ceilometer_dashboard
+ sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord/ceilometer.yaml
+
ceilometer_dashboard:
#NOTE: The below dashboard install scripts assume
#clouldlab openstack environment created using "OpenStack" profile
@@ -78,3 +80,6 @@
upgrade_pkgs:
sudo pip install httpie --upgrade
+
+cloudlab_ceilometer_custom_images:
+ bash -c "source ../setup/admin-openrc.sh; glance image-show ceilometer-trusty-server-multi-nic || ! mkdir -p /tmp/images || ! wget http://www.vicci.org/cord/ceilometer-trusty-server-multi-nic.qcow2 -P /tmp/images || glance image-create --name ceilometer-trusty-server-multi-nic --disk-format qcow2 --file /tmp/images/ceilometer-trusty-server-multi-nic.qcow2 --container-format bare"
diff --git a/xos/configurations/cord/ceilometer.yaml b/xos/configurations/cord/ceilometer.yaml
index a5655b4..ff282e3 100644
--- a/xos/configurations/cord/ceilometer.yaml
+++ b/xos/configurations/cord/ceilometer.yaml
@@ -123,18 +123,18 @@
view_url: /admin/ceilometer/ceilometerservice/$id$/
kind: ceilometer
ceilometer_pub_sub_url: http://10.11.10.1:4455/
-# public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
-# artifacts:
-# pubkey: /opt/xos/synchronizers/vcpe/vcpe_public_key
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ artifacts:
+ pubkey: /opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key
- service_sflow:
- type: tosca.nodes.SFlowService
- requirements:
- properties:
- view_url: /admin/ceilometer/sflowservice/$id$/
- kind: sflow
- sflow_port: 6343
- sflow_api_port: 33333
+# service_sflow:
+# type: tosca.nodes.SFlowService
+# requirements:
+# properties:
+# view_url: /admin/ceilometer/sflowservice/$id$/
+# kind: sflow
+# sflow_port: 6343
+# sflow_api_port: 33333
Private:
type: tosca.nodes.NetworkTemplate
@@ -161,6 +161,9 @@
trusty-server-multi-nic:
type: tosca.nodes.Image
+ ceilometer-trusty-server-multi-nic:
+ type: tosca.nodes.Image
+
mysite_ceilometer:
description: Ceilometer Proxy Slice
type: tosca.nodes.Slice
@@ -172,21 +175,21 @@
node: mysite
relationship: tosca.relationships.MemberOfSite
- default_image:
- node: trusty-server-multi-nic
+ node: ceilometer-trusty-server-multi-nic
relationship: tosca.relationships.DefaultImage
properties:
default_flavor: m1.small
- mysite_sflow:
- description: Slice for sFlow service
- type: tosca.nodes.Slice
- requirements:
- - sflow_service:
- node: service_sflow
- relationship: tosca.relationships.MemberOfService
- - site:
- node: mysite
- relationship: tosca.relationships.MemberOfSite
+# mysite_sflow:
+# description: Slice for sFlow service
+# type: tosca.nodes.Slice
+# requirements:
+# - sflow_service:
+# node: service_sflow
+# relationship: tosca.relationships.MemberOfService
+# - site:
+# node: mysite
+# relationship: tosca.relationships.MemberOfSite
my_ceilometer_tenant:
description: Ceilometer Service default Tenant
@@ -197,27 +200,27 @@
relationship: tosca.relationships.MemberOfService
# Virtual machines
- sflow_service_instance:
- type: tosca.nodes.Compute
- capabilities:
- # Host container properties
- host:
- properties:
- num_cpus: 1
- disk_size: 10 GB
- mem_size: 4 MB
- # Guest Operating System properties
- os:
- properties:
- # host Operating System image properties
- architecture: x86_64
- type: linux
- distribution: Ubuntu
- version: 14.10
- requirements:
- - slice:
- node: mysite_sflow
- relationship: tosca.relationships.MemberOfSlice
+# sflow_service_instance:
+# type: tosca.nodes.Compute
+# capabilities:
+# # Host container properties
+# host:
+# properties:
+# num_cpus: 1
+# disk_size: 10 GB
+# mem_size: 4 MB
+# # Guest Operating System properties
+# os:
+# properties:
+# # host Operating System image properties
+# architecture: x86_64
+# type: linux
+# distribution: Ubuntu
+# version: 14.10
+# requirements:
+# - slice:
+# node: mysite_sflow
+# relationship: tosca.relationships.MemberOfSlice
Ceilometer:
type: tosca.nodes.DashboardView
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 07d4b68..4dea365 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -41,6 +41,7 @@
backend_network_label: hpc_client
public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
private_key_fn: /opt/xos/synchronizers/vcpe/vcpe_private_key
+# node_label: label_vsg
artifacts:
pubkey: /opt/xos/synchronizers/vcpe/vcpe_public_key
@@ -270,6 +271,8 @@
mysite:
type: tosca.nodes.Site
+ label_vsg:
+ type: tosca.nodes.NodeLabel
# CORD Slices
mysite_vsg:
diff --git a/xos/configurations/cord/make-vtn-networkconfig-json.sh b/xos/configurations/cord/make-vtn-networkconfig-json.sh
index 2cccd65..77b855d 100644
--- a/xos/configurations/cord/make-vtn-networkconfig-json.sh
+++ b/xos/configurations/cord/make-vtn-networkconfig-json.sh
@@ -74,8 +74,8 @@
]
}
},
- "org.onosproject.openstackswitching" : {
- "openstackswitching" : {
+ "org.onosproject.openstackinterface" : {
+ "openstackinterface" : {
"do_not_push_flows" : "true",
"neutron_server" : "http://$NEUTRONIP:9696/v2.0/",
"keystone_server" : "http://$KEYSTONEIP:5000/v2.0/",
diff --git a/xos/configurations/vtn/cord-vtn-vsg.yaml b/xos/configurations/vtn/cord-vtn-vsg.yaml
index 1b26bba..c228a80 100644
--- a/xos/configurations/vtn/cord-vtn-vsg.yaml
+++ b/xos/configurations/vtn/cord-vtn-vsg.yaml
@@ -21,7 +21,7 @@
public_addresses:
type: tosca.nodes.AddressPool
properties:
- addresses: 10.123.0.0/24 10.124.0.0/24
+ addresses: 10.123.0.128/25
service_vsg:
type: tosca.nodes.VSGService
@@ -38,6 +38,7 @@
wan_container_gateway_mac: 00:8c:fa:5b:09:d8
wan_container_netbits: 24
dns_servers: 8.8.8.8, 8.8.4.4
+# node_label: label_vsg
artifacts:
pubkey: /opt/xos/synchronizers/vcpe/vcpe_public_key
@@ -62,6 +63,9 @@
mysite:
type: tosca.nodes.Site
+ label_vsg:
+ type: tosca.nodes.NodeLabel
+
# Networks required by the CORD setup
mysite_vsg-access:
type: tosca.nodes.network.Network
diff --git a/xos/configurations/vtn/docker-compose.yml b/xos/configurations/vtn/docker-compose.yml
index 0fa718b..1839f78 100644
--- a/xos/configurations/vtn/docker-compose.yml
+++ b/xos/configurations/vtn/docker-compose.yml
@@ -17,7 +17,7 @@
- ../setup:/root/setup:ro
- ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
- ./files/xos_vtn_config:/opt/xos/xos_configuration/xos_vtn_config:ro
-
+ - ./images:/opt/xos/images:ro
xos_synchronizer_onos:
image: xosproject/xos-synchronizer-openstack
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 28d99fd..910ee97 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1266,6 +1266,14 @@
list_display_links = ('backend_status_icon', 'name', )
class NodeForm(forms.ModelForm):
+ labels = forms.ModelMultipleChoiceField(
+ queryset=NodeLabel.objects.all(),
+ required=False,
+ help_text="Select which labels apply to this node",
+ widget=FilteredSelectMultiple(
+ verbose_name=('Labels'), is_stacked=False
+ )
+ )
class Meta:
model = Node
widgets = {
@@ -1273,6 +1281,31 @@
'deployment': LinkedSelect
}
+ def __init__(self, *args, **kwargs):
+ request = kwargs.pop('request', None)
+ super(NodeForm, self).__init__(*args, **kwargs)
+
+ if self.instance and self.instance.pk:
+ self.fields['labels'].initial = self.instance.labels.all()
+
+ def save(self, commit=True):
+ node = super(NodeForm, self).save(commit=False)
+
+ node.labels = self.cleaned_data['labels']
+
+ if commit:
+ node.save()
+
+ return node
+
+
+class NodeLabelAdmin(XOSBaseAdmin):
+ list_display = ('name',)
+ list_display_links = ('name', )
+
+ fields = ('name', )
+
+
class NodeAdmin(XOSBaseAdmin):
form = NodeForm
list_display = ('backend_status_icon', 'name', 'site_deployment')
@@ -1280,13 +1313,14 @@
list_filter = ('site_deployment',)
inlines = [TagInline,InstanceInline]
- fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name', 'site_deployment'], 'classes':['suit-tab suit-tab-details']})]
+ fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name', 'site_deployment'], 'classes':['suit-tab suit-tab-details']}),
+ ('Labels', {'fields': ['labels'], 'classes':['suit-tab suit-tab-labels']})]
readonly_fields = ('backend_status_text', )
user_readonly_fields = ['name','site_deployment']
user_readonly_inlines = [TagInline,InstanceInline]
- suit_form_tabs =(('details','Node Details'),('instances','Instances'))
+ suit_form_tabs =(('details','Node Details'),('instances','Instances'), ('labels', 'Labels'))
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'site':
@@ -2107,6 +2141,7 @@
admin.site.register(SiteRole)
admin.site.register(SliceRole)
admin.site.register(Node, NodeAdmin)
+ admin.site.register(NodeLabel, NodeLabelAdmin)
#admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
#admin.site.register(SitePrivilege, SitePrivilegeAdmin)
admin.site.register(Instance, InstanceAdmin)
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index 628a3bb..2ee6b94 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -21,7 +21,7 @@
from .credential import UserCredential,SiteCredential,SliceCredential
from .site import SiteRole
from .site import SitePrivilege
-from .node import Node
+from .node import Node, NodeLabel
from .slicetag import SliceTag
from .instance import Instance
from .reservation import ReservedResource
diff --git a/xos/core/models/node.py b/xos/core/models/node.py
index 5496d6b..b825787 100644
--- a/xos/core/models/node.py
+++ b/xos/core/models/node.py
@@ -28,3 +28,9 @@
def can_update(self, user):
return user.can_update_site(self.site, allow=['tech'])
+
+class NodeLabel(PlCoreBase):
+ name = StrippedCharField(max_length=200, help_text="label name", unique=True)
+ node = models.ManyToManyField(Node, related_name="labels", blank=True)
+
+ def __unicode__(self): return u'%s' % (self.name)
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 51f9b3c..ee28cf6 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -437,12 +437,21 @@
class LeastLoadedNodeScheduler(Scheduler):
# This scheduler always return the node with the fewest number of instances.
- def __init__(self, slice):
+ def __init__(self, slice, label=None):
super(LeastLoadedNodeScheduler, self).__init__(slice)
+ self.label = label
def pick(self):
from core.models import Node
- nodes = list(Node.objects.all())
+ nodes = Node.objects.all()
+
+ if self.label:
+ nodes = nodes.filter(labels__name=self.label)
+
+ nodes = list(nodes)
+
+ 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.
diff --git a/xos/services/ceilometer/models.py b/xos/services/ceilometer/models.py
index 65bffef..d8fb0fa 100644
--- a/xos/services/ceilometer/models.py
+++ b/xos/services/ceilometer/models.py
@@ -36,7 +36,8 @@
KIND = CEILOMETER_KIND
LOOK_FOR_IMAGES=[ #"trusty-server-multi-nic-docker", # CloudLab
- "trusty-server-multi-nic",
+ "ceilometer-trusty-server-multi-nic",
+ #"trusty-server-multi-nic",
]
diff --git a/xos/services/cord/admin.py b/xos/services/cord/admin.py
index 4ec4c6f..56e46ae 100644
--- a/xos/services/cord/admin.py
+++ b/xos/services/cord/admin.py
@@ -108,6 +108,7 @@
wan_container_gateway_mac = forms.CharField(required=False)
wan_container_netbits = forms.CharField(required=False)
dns_servers = forms.CharField(required=False)
+ node_label = forms.CharField(required=False)
def __init__(self,*args,**kwargs):
super (VSGServiceForm,self ).__init__(*args,**kwargs)
@@ -121,6 +122,7 @@
self.fields['wan_container_gateway_mac'].initial = self.instance.wan_container_gateway_mac
self.fields['wan_container_netbits'].initial = self.instance.wan_container_netbits
self.fields['dns_servers'].initial = self.instance.dns_servers
+ self.fields['node_label'].initial = self.instance.node_label
def save(self, commit=True):
self.instance.bbs_api_hostname = self.cleaned_data.get("bbs_api_hostname")
@@ -132,6 +134,7 @@
self.instance.wan_container_gateway_mac = self.cleaned_data.get("wan_container_gateway_mac")
self.instance.wan_container_netbits = self.cleaned_data.get("wan_container_netbits")
self.instance.dns_servers = self.cleaned_data.get("dns_servers")
+ self.instance.node_label = self.cleaned_data.get("node_label")
return super(VSGServiceForm, self).save(commit=commit)
class Meta:
@@ -143,7 +146,7 @@
verbose_name_plural = "vSG Service"
list_display = ("backend_status_icon", "name", "enabled")
list_display_links = ('backend_status_icon', 'name', )
- fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description', "view_url", "icon_url", "service_specific_attribute",],
+ fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description', "view_url", "icon_url", "service_specific_attribute", "node_label"],
'classes':['suit-tab suit-tab-general']}),
("backend config", {'fields': [ "backend_network_label", "bbs_api_hostname", "bbs_api_port", "bbs_server", "bbs_slice"],
'classes':['suit-tab suit-tab-backend']}),
diff --git a/xos/services/cord/models.py b/xos/services/cord/models.py
index 9ff2dcb..b7f25af 100644
--- a/xos/services/cord/models.py
+++ b/xos/services/cord/models.py
@@ -369,7 +369,8 @@
("wan_container_gateway_ip", ""),
("wan_container_gateway_mac", ""),
("wan_container_netbits", "24"),
- ("dns_servers", "8.8.8.8") )
+ ("dns_servers", "8.8.8.8"),
+ ("node_label", None) )
def __init__(self, *args, **kwargs):
super(VSGService, self).__init__(*args, **kwargs)
@@ -644,6 +645,9 @@
slice = self.provider_service.slices.all()[0]
return slice
+ def get_vsg_service(self):
+ return VSGService.get_service_objects().get(id=self.provider_service.id)
+
def find_instance_for_s_tag(self, s_tag):
#s_tags = STagBlock.objects.find(s_s_tag)
#if s_tags:
@@ -669,7 +673,7 @@
if slice.default_isolation == "container_vm":
(node, parent) = ContainerVmScheduler(slice).pick()
else:
- (node, parent) = LeastLoadedNodeScheduler(slice).pick()
+ (node, parent) = LeastLoadedNodeScheduler(slice, label=self.get_vsg_service().node_label).pick()
instance = Instance(slice = slice,
node = node,
diff --git a/xos/services/exampleservice/README.md b/xos/services/exampleservice/README.md
new file mode 100644
index 0000000..ce6210d
--- /dev/null
+++ b/xos/services/exampleservice/README.md
@@ -0,0 +1,8 @@
+# ExampleService
+
+This is an example XOS service, specifically the Django Model and Admin.
+
+The Synchronizer corresponding to this service can be found in `../../synchronizers/exampleservice`.
+
+Documentation for this is located here: [XOS Guide : DevGuide : ExampleService](http://guide.xosproject.org/devguide/exampleservice/).
+
diff --git a/xos/services/exampleservice/__init__.py b/xos/services/exampleservice/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/services/exampleservice/__init__.py
diff --git a/xos/services/exampleservice/admin.py b/xos/services/exampleservice/admin.py
new file mode 100644
index 0000000..d4f6248
--- /dev/null
+++ b/xos/services/exampleservice/admin.py
@@ -0,0 +1,101 @@
+# admin.py - ExampleService Django Admin
+
+from core.admin import ReadOnlyAwareAdmin, SliceInline
+from core.middleware import get_request
+from core.models import User
+
+from django import forms
+from django.contrib import admin
+
+from services.exampleservice.models import *
+
+class ExampleServiceAdmin(ReadOnlyAwareAdmin):
+
+ model = ExampleService
+ verbose_name = SERVICE_NAME_VERBOSE
+ verbose_name_plural = SERVICE_NAME_VERBOSE_PLURAL
+
+ list_display = ('backend_status_icon', 'name', 'enabled',)
+ list_display_links = ('backend_status_icon', 'name', )
+
+ fieldsets = [(None, {
+ 'fields': ['backend_status_text', 'name', 'enabled', 'versionNumber', 'description',],
+ 'classes':['suit-tab suit-tab-general',],
+ })]
+
+ readonly_fields = ('backend_status_text', )
+ user_readonly_fields = ['name', 'enabled', 'versionNumber', 'description',]
+
+ inlines = [SliceInline]
+
+ extracontext_registered_admins = True
+
+ suit_form_tabs = (
+ ('general', 'Example Service Details', ),
+ ('slices', 'Slices',),
+ )
+
+ suit_form_includes = ((
+ 'top',
+ 'administration'),
+ )
+
+ def queryset(self, request):
+ return ExampleService.get_service_objects_by_user(request.user)
+
+admin.site.register(ExampleService, ExampleServiceAdmin)
+
+class ExampleTenantForm(forms.ModelForm):
+
+ class Meta:
+ model = ExampleTenant
+
+ creator = forms.ModelChoiceField(queryset=User.objects.all())
+
+ def __init__(self, *args, **kwargs):
+ super(ExampleTenantForm, self).__init__(*args, **kwargs)
+
+ self.fields['kind'].widget.attrs['readonly'] = True
+ self.fields['kind'].initial = SERVICE_NAME
+
+ self.fields['provider_service'].queryset = ExampleService.get_service_objects().all()
+
+ if self.instance:
+ self.fields['creator'].initial = self.instance.creator
+ self.fields['tenant_message'].initial = self.instance.tenant_message
+
+ if (not self.instance) or (not self.instance.pk):
+ self.fields['creator'].initial = get_request().user
+ if ExampleService.get_service_objects().exists():
+ self.fields['provider_service'].initial = ExampleService.get_service_objects().all()[0]
+
+ def save(self, commit=True):
+ self.instance.creator = self.cleaned_data.get('creator')
+ self.instance.tenant_message = self.cleaned_data.get('tenant_message')
+ return super(ExampleTenantForm, self).save(commit=commit)
+
+
+class ExampleTenantAdmin(ReadOnlyAwareAdmin):
+
+ verbose_name = TENANT_NAME_VERBOSE
+ verbose_name_plural = TENANT_NAME_VERBOSE_PLURAL
+
+ list_display = ('id', 'backend_status_icon', 'instance', 'tenant_message')
+ list_display_links = ('backend_status_icon', 'instance', 'tenant_message', 'id')
+
+ fieldsets = [(None, {
+ 'fields': ['backend_status_text', 'kind', 'provider_service', 'instance', 'creator', 'tenant_message'],
+ 'classes': ['suit-tab suit-tab-general'],
+ })]
+
+ readonly_fields = ('backend_status_text', 'instance',)
+
+ form = ExampleTenantForm
+
+ suit_form_tabs = (('general', 'Details'),)
+
+ def queryset(self, request):
+ return ExampleTenant.get_tenant_objects_by_user(request.user)
+
+admin.site.register(ExampleTenant, ExampleTenantAdmin)
+
diff --git a/xos/services/exampleservice/models.py b/xos/services/exampleservice/models.py
new file mode 100644
index 0000000..6305b54
--- /dev/null
+++ b/xos/services/exampleservice/models.py
@@ -0,0 +1,52 @@
+# models.py - ExampleService Models
+
+from core.models import Service, TenantWithContainer
+from django.db import models, transaction
+
+SERVICE_NAME = 'exampleservice'
+SERVICE_NAME_VERBOSE = 'Example Service'
+SERVICE_NAME_VERBOSE_PLURAL = 'Example Services'
+TENANT_NAME_VERBOSE = 'Example Tenant'
+TENANT_NAME_VERBOSE_PLURAL = 'Example Tenants'
+
+class ExampleService(Service):
+
+ KIND = SERVICE_NAME
+
+ class Meta:
+ app_label = SERVICE_NAME
+ verbose_name = SERVICE_NAME_VERBOSE
+ proxy = True
+
+class ExampleTenant(TenantWithContainer):
+
+ KIND = SERVICE_NAME
+
+ class Meta:
+ verbose_name = TENANT_NAME_VERBOSE
+
+ tenant_message = models.CharField(max_length=254, help_text="Tenant Message to Display")
+
+ def __init__(self, *args, **kwargs):
+ exampleservice = ExampleService.get_service_objects().all()
+ if exampleservice:
+ self._meta.get_field('provider_service').default = exampleservice[0].id
+ super(ExampleTenant, self).__init__(*args, **kwargs)
+
+ def save(self, *args, **kwargs):
+ super(ExampleTenant, self).save(*args, **kwargs)
+ model_policy_exampletenant(self.pk)
+
+ def delete(self, *args, **kwargs):
+ self.cleanup_container()
+ super(ExampleTenant, self).delete(*args, **kwargs)
+
+
+def model_policy_exampletenant(pk):
+ with transaction.atomic():
+ tenant = ExampleTenant.objects.select_for_update().filter(pk=pk)
+ if not tenant:
+ return
+ tenant = tenant[0]
+ tenant.manage_container()
+
diff --git a/xos/synchronizers/exampleservice/exampleservice-synchronizer.py b/xos/synchronizers/exampleservice/exampleservice-synchronizer.py
new file mode 100644
index 0000000..90d2c98
--- /dev/null
+++ b/xos/synchronizers/exampleservice/exampleservice-synchronizer.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+# Runs the standard XOS synchronizer
+
+import importlib
+import os
+import sys
+
+synchronizer_path = os.path.join(os.path.dirname(
+ os.path.realpath(__file__)), "../../synchronizers/base")
+sys.path.append(synchronizer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
+
diff --git a/xos/synchronizers/exampleservice/exampleservice_config b/xos/synchronizers/exampleservice/exampleservice_config
new file mode 100644
index 0000000..7e59fdd
--- /dev/null
+++ b/xos/synchronizers/exampleservice/exampleservice_config
@@ -0,0 +1,24 @@
+# Required by XOS
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+# Required by XOS
+[api]
+nova_enabled=True
+
+# Sets options for the synchronizer
+[observer]
+name=exampleservice
+dependency_graph=/opt/xos/synchronizers/exampleservice/model-deps
+steps_dir=/opt/xos/synchronizers/exampleservice/steps
+sys_dir=/opt/xos/synchronizers/exampleservice/sys
+logfile=/var/log/xos_backend.log
+pretend=False
+backoff_disabled=True
+save_ansible_output=True
+proxy_ssh=False
+
diff --git a/xos/synchronizers/exampleservice/model-deps b/xos/synchronizers/exampleservice/model-deps
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/xos/synchronizers/exampleservice/model-deps
@@ -0,0 +1 @@
+{}
diff --git a/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml b/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
new file mode 100644
index 0000000..9da06f5
--- /dev/null
+++ b/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
@@ -0,0 +1,18 @@
+---
+# exampletenant_playbook
+
+- hosts: "{{ instance_name }}"
+ connection: ssh
+ user: ubuntu
+ sudo: yes
+ gather_facts: no
+
+ tasks:
+ - name: install apache
+ apt:
+ name=apache2
+ update_cache=yes
+
+ - name: write message
+ shell: echo "{{ tenant_message }}" > /var/www/html/index.html
+
diff --git a/xos/synchronizers/exampleservice/steps/sync_exampletenant.py b/xos/synchronizers/exampleservice/steps/sync_exampletenant.py
new file mode 100644
index 0000000..bc4169b
--- /dev/null
+++ b/xos/synchronizers/exampleservice/steps/sync_exampletenant.py
@@ -0,0 +1,40 @@
+import os
+import sys
+from django.db.models import Q, F
+from services.exampleservice.models import ExampleService, ExampleTenant
+from synchronizers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+class SyncExampleTenant(SyncInstanceUsingAnsible):
+
+ provides = [ExampleTenant]
+
+ observes = ExampleTenant
+
+ requested_interval = 0
+
+ template_name = "exampletenant_playbook.yaml"
+
+ service_key_name = "/opt/xos/synchronizers/exampleservice/exampleservice_private_key"
+
+ def __init__(self, *args, **kwargs):
+ super(SyncExampleTenant, self).__init__(*args, **kwargs)
+
+ def fetch_pending(self, deleted):
+
+ if (not deleted):
+ objs = ExampleTenant.get_tenant_objects().filter(
+ Q(enacted__lt=F('updated')) | Q(enacted=None), Q(lazy_blocked=False))
+ else:
+ # If this is a deletion we get all of the deleted tenants..
+ objs = ExampleTenant.get_deleted_tenant_objects()
+
+ return objs
+
+ # Gets the attributes that are used by the Ansible template but are not
+ # part of the set of default attributes.
+ def get_extra_attributes(self, o):
+ return {"tenant_message": o.tenant_message}
+
diff --git a/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml b/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml
index 06403a6..1c4da12 100644
--- a/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml
+++ b/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml
@@ -32,38 +32,38 @@
- remove container
{% else %}
{% if full_setup %}
- - name: Docker repository
- copy: src=/opt/xos/synchronizers/monitoring_channel/files/docker.list
- dest=/etc/apt/sources.list.d/docker.list
-
- - name: Import the repository key
- apt_key: keyserver=keyserver.ubuntu.com id=36A1D7869245C8950F966E92D8576A8BA88D21E9
-
- - name: install Docker
- apt: name=lxc-docker state=present update_cache=yes
-
- - name: install python-setuptools
- apt: name=python-setuptools state=present
-
- - name: install pip
- easy_install: name=pip
-
- - name: install docker-py
- pip: name=docker-py version=0.5.3
-
- - name: install Pipework
- get_url: url=https://raw.githubusercontent.com/jpetazzo/pipework/master/pipework
- dest=/usr/local/bin/pipework
- mode=0755
-
- - name: Disable resolvconf service
- shell: service resolvconf stop
- shell: echo manual > /etc/init/resolvconf.override
- shell: rm -f /etc/resolv.conf
-
- - name: Install resolv.conf
- copy: src=/opt/xos/synchronizers/monitoring_channel/files/vm-resolv.conf
- dest=/etc/resolv.conf
+# - name: Docker repository
+# copy: src=/opt/xos/synchronizers/monitoring_channel/files/docker.list
+# dest=/etc/apt/sources.list.d/docker.list
+#
+# - name: Import the repository key
+# apt_key: keyserver=keyserver.ubuntu.com id=36A1D7869245C8950F966E92D8576A8BA88D21E9
+#
+# - name: install Docker
+# apt: name=lxc-docker state=present update_cache=yes
+#
+# - name: install python-setuptools
+# apt: name=python-setuptools state=present
+#
+# - name: install pip
+# easy_install: name=pip
+#
+# - name: install docker-py
+# pip: name=docker-py version=0.5.3
+#
+# - name: install Pipework
+# get_url: url=https://raw.githubusercontent.com/jpetazzo/pipework/master/pipework
+# dest=/usr/local/bin/pipework
+# mode=0755
+#
+# - name: Disable resolvconf service
+# shell: service resolvconf stop
+# shell: echo manual > /etc/init/resolvconf.override
+# shell: rm -f /etc/resolv.conf
+#
+# - name: Install resolv.conf
+# copy: src=/opt/xos/synchronizers/monitoring_channel/files/vm-resolv.conf
+# dest=/etc/resolv.conf
{% endif %}
- name: ceilometer proxy config
diff --git a/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2 b/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2
index a0ebb0c..1685e07 100755
--- a/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2
+++ b/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2
@@ -21,7 +21,7 @@
if [ "$?" == 1 ]
then
#sudo docker build -t monitoring-channel -f Dockerfile.monitoring_channel .
- sudo docker pull srikanthvavila/monitoring-channel
+ #sudo docker pull srikanthvavila/monitoring-channel
if [ -z "$HEADNODEFLATLANIP" ] || [ "$HEADNODEFLATLANIP" == "None" ]
then
docker run -d --name=$MONITORING_CHANNEL --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 -v /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config:/usr/local/share/ceilometer_proxy_config srikanthvavila/monitoring-channel
@@ -43,4 +43,4 @@
#cat /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config | sudo docker exec -i $MONITORING_CHANNEL bash -c 'cat > /usr/local/share/ceilometer_proxy_config'
# Attach to container
-docker start -a $MONITORING_CHANNEL
+#docker start -a $MONITORING_CHANNEL
diff --git a/xos/synchronizers/openstack/steps/sync_controller_networks.py b/xos/synchronizers/openstack/steps/sync_controller_networks.py
index f8b2292..5375a8d 100644
--- a/xos/synchronizers/openstack/steps/sync_controller_networks.py
+++ b/xos/synchronizers/openstack/steps/sync_controller_networks.py
@@ -1,5 +1,7 @@
import os
import base64
+import struct
+import socket
from collections import defaultdict
from netaddr import IPAddress, IPNetwork
from django.db.models import F, Q
@@ -35,10 +37,17 @@
return cidr
def alloc_gateway(self, subnet):
- parts = subnet.split(".")
- if len(parts)!=4:
- raise Exception("Invalid subnet %s" % subnet)
- return ".".join(parts[:3]) + ".1"
+ # given a CIDR, allocate a default gateway using the .1 address within
+ # the subnet.
+ # 10.123.0.0/24 --> 10.123.0.1
+ # 207.141.192.128/28 --> 207.141.192.129
+ (network, bits) = subnet.split("/")
+ network=network.strip()
+ bits=int(bits.strip())
+ netmask = (~(pow(2,32-bits)-1) & 0xFFFFFFFF)
+ ip = struct.unpack("!L", socket.inet_aton(network))[0]
+ ip = ip & netmask | 1
+ return socket.inet_ntoa(struct.pack("!L", ip))
def save_controller_network(self, controller_network):
network_name = controller_network.network.name
diff --git a/xos/tools/apigen/fields.template.txt b/xos/tools/apigen/fields.template.txt
new file mode 100644
index 0000000..ad5a0b4
--- /dev/null
+++ b/xos/tools/apigen/fields.template.txt
@@ -0,0 +1,6 @@
+{% for object in generator.all %}
+Object {{ object }}:
+Fields:
+{% for field in object.fields %}{{ field.name }}:{{ field.type }}
+{% endfor %}
+{% endfor %}
diff --git a/xos/tools/apigen/modelgen b/xos/tools/apigen/modelgen
index 414540d..068b7cb 100644
--- a/xos/tools/apigen/modelgen
+++ b/xos/tools/apigen/modelgen
@@ -11,10 +11,14 @@
# Django set up
+import django
sys.path.append('.')
+sys.path.append('/opt/xos')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
from django.db.models.fields.related import ForeignKey, ManyToManyField
+django.setup()
+
options = None
@@ -30,7 +34,9 @@
def enum_classes(apps):
global app_map
+ global class_map
app_map = {}
+ class_map = {}
model_classes = []
for app in apps:
orig_app=app
@@ -49,6 +55,16 @@
if type(c)==type(PlCoreBase) and c.__name__ not in options.blacklist:
model_classes.append(c)
app_map[c.__name__]=orig_app
+ c.class_name = c.__name__
+ file_name = c.__module__.rsplit('.',1)[1]
+ try:
+ if (file_name not in class_map[orig_app]):
+ class_map[orig_app].append({file_name:[c]})
+ else:
+ class_map[orig_app][file_name].append(c)
+
+ except KeyError:
+ class_map[orig_app] = [{file_name:[c]}]
return model_classes
@@ -98,6 +114,9 @@
return name
class Generator(dict):
+ def __init__(self):
+ self.apps = {}
+
def all(self):
return self.values()
@@ -117,7 +136,19 @@
obj.app = app_map[o.__name__]
except KeyError:
print "KeyError: %r"%o.__name__
- pdb.set_trace()
+ obj.class_name = o.class_name
+
+ file_name = o.__module__.rsplit('.',1)[1]
+
+ try:
+ if (file_name not in self.apps[obj.app]):
+ self.apps[obj.app][file_name]=[obj]
+ else:
+ self.apps[obj.app][file_name].append(obj)
+
+ except KeyError:
+ self.apps[obj.app] = {file_name:[obj]}
+
self[str(obj).lower()]=obj
def compute_links(self):
@@ -150,7 +181,8 @@
pass
else:
f.type = f.__class__.__name__
-
+ if (type(f)==ForeignKey):
+ f.related.model.class_name = f.related.model.__name__
if (f.name not in base_props):
obj.fields.append(f)
obj.props.append(f.name)
@@ -189,6 +221,9 @@
global options
parser = OptionParser(usage="modelgen [options] <template_fn>", )
+ parser.add_option("-d", "--dict", dest="dict",
+ help="dictionary to replace text in output", metavar="DICT", default=[], action="append")
+
parser.add_option("-a", "--app", dest="apps",
help="list of applications to parse", metavar="APP", default=[], action="append")
parser.add_option("-b", "--blacklist", dest="blacklist",
@@ -228,7 +263,31 @@
template_contents = open(template_name).read()
template = Template(template_contents)
context = Context({'generator':generator})
- print template.render(context)
+ rendered = template.render(context)
+ lines = rendered.splitlines()
+ current_buffer = []
+ for l in lines:
+ if (l.startswith('+++')):
+ filename = l[4:]
+ fil = open(filename,'w')
+ buf = '\n'.join(current_buffer)
+
+ obuf = buf
+ for d in options.dict:
+ df = open(d).read()
+ d = json.loads(df)
+
+ pattern = re.compile(r'\b(' + '|'.join(d.keys()) + r')\b')
+ obuf = pattern.sub(lambda x: d[x.group()], buf)
+ fil.write(obuf)
+ fil.close()
+
+ print 'Written to file %s'%filename
+ current_buffer = []
+ else:
+ current_buffer.append(l)
+ if (current_buffer):
+ print '\n'.join(current_buffer)
if (__name__=='__main__'):
diff --git a/xos/tools/apigen/modelyaml.fields.template.txt b/xos/tools/apigen/modelyaml.fields.template.txt
new file mode 100644
index 0000000..69129ed
--- /dev/null
+++ b/xos/tools/apigen/modelyaml.fields.template.txt
@@ -0,0 +1,12 @@
+{% for object in generator.all %}
+{{ object.camel }}:
+fields:
+ {% for f in object.fields %}
+ - name: {{ f.name }}
+ type: {{ f.type }}
+ {% if f.help_text %}
+ help_text: {{ f.help_text }}
+ {% endif %}
+ null: {{ f.null }}
+{% endfor %}
+{% endfor %}
diff --git a/xos/tools/apigen/synchronizer.template.txt b/xos/tools/apigen/synchronizer.template.txt
new file mode 100644
index 0000000..c784e21
--- /dev/null
+++ b/xos/tools/apigen/synchronizer.template.txt
@@ -0,0 +1,71 @@
+{% for app,files in generator.apps.items %}
+{% for file,models in files.items %}
+{% for m in models %}
+# This file implements the synchronization of the {{ m.class_name }} model
+# TODO (see below):
+
+import os
+import base64
+import socket
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from {{app}}.models.{{ file }} import {{ m.class_name.title }}
+from synchronizers.base.ansible import *
+from synchronizers.base.syncstep import *
+from xos.logger import observer_logger as logger
+
+class Sync{{m.camel.title}}(SyncStep):
+ # The model synchronized
+ provides=[{{m.class_name.title}}]
+ # How often do you want this to run. 0 means immediately following changes to the model
+ requested_interval=0
+ # The model in which changes trigger this module
+ observes={{m.class_name}}
+ # The Ansible recipe that does the bulk of the synchronization (hopefully)
+ playbook='sync_{{m.class_name}}.yaml'
+
+ # 1. Populate a data structure to pass into your ansible recipe, based on the data model.
+ def map_sync_inputs(self, {{ m.class_name }}):
+ fields = {
+ {% for f in m.fields %}'{{f.name}}': {{f.name}},
+ {% endfor %}
+ }
+ return fields
+
+ # 2. Store the result of the operation in the model
+ def map_sync_outputs(self, {{ m.class_name }}, res):
+ // Store the output of res in the {{m.class_name}} object
+ return
+
+ # 3. Populate the data structure that identifies objects to delete
+ def map_delete_inputs(self, {{ m.class_name }}, res):
+ fields = {
+ {% for f in m.fields %}'{{f.name}}': {{f.name}},
+ {% endfor %}
+ 'delete':True
+ }
+ return fields
+
+
++++ Sync{{ m.class_name }}.py
+---
+- hosts: 127.0.0.1
+ connection: local
+ tasks:
+ - task_1:
+ {% for f in m.fields %}
+ {{f.name}}: {% templatetag openbrace %}{% templatetag openbrace %}{{f.name}}{% templatetag closebrace %}{% templatetag closebrace %}
+ {% endfor %}
+ {% verbatim %}
+ {% if delete %}
+ state: absent
+ {% else %}
+ state: present
+ {% endif %}
+ {% endif %}
+ {% endverbatim %}
++++ Sync{{ m.class_name }}.yaml
+{% endfor %}
+{% endfor %}
+{% endfor %}
diff --git a/xos/tools/apigen/type_map b/xos/tools/apigen/type_map
new file mode 100644
index 0000000..ac4a8a8
--- /dev/null
+++ b/xos/tools/apigen/type_map
@@ -0,0 +1,10 @@
+{
+ "AutoField":"int64",
+ "ForeignKey":"InstanceIdentifier",
+ "CharField":"string",
+ "TextField":"string",
+ "FloatField":"float64",
+ "BooleanField":"boolean",
+ "StrippedCharField":"string",
+ "DateTimeField":"date-and-time"
+}
diff --git a/xos/tools/apigen/yang.template.txt b/xos/tools/apigen/yang.template.txt
new file mode 100644
index 0000000..ad4b1b1
--- /dev/null
+++ b/xos/tools/apigen/yang.template.txt
@@ -0,0 +1,21 @@
+{% for app,files in generator.apps.items %}
+{% for file,m in files.items %}
+module xos-{{ app }}-{{ file }} {
+ namespace "urn:xos:{{app}}.{{ file }}";
+ prefix xos-cs;
+
+ import complex-types {prefix ct;}
+ revision 2016-2-24 {
+ description "Initial";
+ }
+
+ complex-type {{ m.class_name }} {
+ {% for f in m.fields %}
+
+ leaf {{ f.name }} { type {{ f.type }}{% ifequal f.type "ForeignKey" %} { ct:instance-type {{f.related.model.class_name}};{% if f.null%}{%else%}require-instance true{% endif %}{% endifequal %};{% if f.max_length %} { length {{ f.max_length }};{% endif %}{% if None %}default "{{ f.default }}";{% endif %}}
+ {% endfor %}
+ }
+}
++++ {{ app }}-{{ file}}.yang
+{% endfor %}
+{% endfor %}
diff --git a/xos/tosca/README.md b/xos/tosca/README.md
new file mode 100644
index 0000000..98f0aaf
--- /dev/null
+++ b/xos/tosca/README.md
@@ -0,0 +1,13 @@
+## TOSCA Interface Definition
+
+This directory implements a TOSCA interface for XOS,
+which can be extended to include specifications for
+service models added to XOS. The directory is organized
+as follows:
+
+ * custom_types -- Defines schema for XOS-specific models.
+ * `.m4` files are source.
+ * `.yaml` files are generated.
+ * definitions -- Defines schema for TOSCA's base models.
+ * resources -- Translates TOSCA specification to Django API.
+ * sample -- Example TOSCA models.
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 15e9710..9773822 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -230,6 +230,9 @@
dns_servers:
type: string
required: false
+ node_label:
+ type: string
+ required: false
tosca.nodes.VBNGService:
derived_from: tosca.nodes.Root
@@ -715,6 +718,16 @@
node:
type: tosca.capabilities.xos.Node
+ tosca.nodes.NodeLabel:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS NodeLabel.
+ properties:
+ xos_base_props
+ capabilities:
+ node:
+ type: tosca.capabilities.xos.NodeLabel
+
tosca.nodes.DashboardView:
derived_from: tosca.nodes.Root
description: >
@@ -849,6 +862,10 @@
derived_from: tosca.relationships.Root
valid_target_types: [ tosca.capabilities.xos.DashboardView ]
+ tosca.relationships.HasLabel:
+ derived_from: tosca.relationships.Root
+ valid_target_types: [ tosca.capabilities.xos.NodeLabel ]
+
tosca.capabilities.xos.Service:
derived_from: tosca.capabilities.Root
description: An XOS Service
@@ -893,6 +910,10 @@
derived_from: tosca.capabilities.Root
description: An XOS Node
+ tosca.capabilities.xos.NodeLabel:
+ derived_from: tosca.capabilities.Root
+ description: An XOS NodeLabel
+
tosca.capabilities.xos.Image:
derived_from: tosca.capabilities.Root
description: An XOS Image
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 88b3388..21e8b8b 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -332,6 +332,9 @@
dns_servers:
type: string
required: false
+ node_label:
+ type: string
+ required: false
tosca.nodes.VBNGService:
derived_from: tosca.nodes.Root
@@ -1005,6 +1008,27 @@
node:
type: tosca.capabilities.xos.Node
+ tosca.nodes.NodeLabel:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS NodeLabel.
+ properties:
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ capabilities:
+ node:
+ type: tosca.capabilities.xos.NodeLabel
+
tosca.nodes.DashboardView:
derived_from: tosca.nodes.Root
description: >
@@ -1150,6 +1174,10 @@
derived_from: tosca.relationships.Root
valid_target_types: [ tosca.capabilities.xos.DashboardView ]
+ tosca.relationships.HasLabel:
+ derived_from: tosca.relationships.Root
+ valid_target_types: [ tosca.capabilities.xos.NodeLabel ]
+
tosca.capabilities.xos.Service:
derived_from: tosca.capabilities.Root
description: An XOS Service
@@ -1194,6 +1222,10 @@
derived_from: tosca.capabilities.Root
description: An XOS Node
+ tosca.capabilities.xos.NodeLabel:
+ derived_from: tosca.capabilities.Root
+ description: An XOS NodeLabel
+
tosca.capabilities.xos.Image:
derived_from: tosca.capabilities.Root
description: An XOS Image
diff --git a/xos/tosca/resources/node.py b/xos/tosca/resources/node.py
index 59b915b..99e756f 100644
--- a/xos/tosca/resources/node.py
+++ b/xos/tosca/resources/node.py
@@ -5,7 +5,7 @@
sys.path.append("/opt/tosca")
from translator.toscalib.tosca_template import ToscaTemplate
-from core.models import Node, Site, Deployment, SiteDeployment
+from core.models import Node, NodeLabel, Site, Deployment, SiteDeployment
from xosresource import XOSResource
@@ -32,6 +32,17 @@
return args
+ def postprocess(self, obj):
+ # We can't set the labels when we create a Node, because they're
+ # ManyToMany related, and the node doesn't exist yet.
+ labels=[]
+ for label_name in self.get_requirements("tosca.relationships.HasLabel"):
+ labels.append(self.get_xos_object(NodeLabel, name=label_name))
+ if labels:
+ self.info("Updated labels for node '%s'" % obj)
+ obj.labels = labels
+ obj.save()
+
def create(self):
nodetemplate = self.nodetemplate
sliceName = nodetemplate.name
diff --git a/xos/tosca/resources/nodelabel.py b/xos/tosca/resources/nodelabel.py
new file mode 100644
index 0000000..9a8df3e
--- /dev/null
+++ b/xos/tosca/resources/nodelabel.py
@@ -0,0 +1,15 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import NodeLabel
+
+from xosresource import XOSResource
+
+class XOSNodeLabel(XOSResource):
+ provides = "tosca.nodes.NodeLabel"
+ xos_model = NodeLabel
+
diff --git a/xos/tosca/resources/vcpeservice.py b/xos/tosca/resources/vcpeservice.py
index 5c7b2a7..2a6a56d 100644
--- a/xos/tosca/resources/vcpeservice.py
+++ b/xos/tosca/resources/vcpeservice.py
@@ -15,5 +15,5 @@
copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key",
"private_key_fn", "versionNumber", "backend_network_label",
"wan_container_gateway_ip", "wan_container_gateway_mac",
- "wan_container_netbits", "dns_servers"]
+ "wan_container_netbits", "dns_servers", "node_label"]