Merge branch 'master' of https://github.com/open-cloud/xos into feature/contentProvider
diff --git a/Dockerfile.cord b/Dockerfile.cord
index 5a2074d..ee0879d 100644
--- a/Dockerfile.cord
+++ b/Dockerfile.cord
@@ -3,3 +3,5 @@
ADD xos/observers/vcpe/supervisor/vcpe-observer.conf /etc/supervisor/conf.d/
RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/vcpe/vcpe_observer_config
+ADD xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf /etc/supervisor/conf.d/
+RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/monitoring_channel/monitoring_channel_observer_config
diff --git a/cloudlab-init.sh b/cloudlab-init.sh
index f275517..9a2c94a 100755
--- a/cloudlab-init.sh
+++ b/cloudlab-init.sh
@@ -22,6 +22,8 @@
then
cp ~/.ssh/id_rsa.pub xos/observers/vcpe/vcpe_public_key
cp ~/.ssh/id_rsa xos/observers/vcpe/vcpe_private_key
+ cp ~/.ssh/id_rsa.pub xos/observers/monitoring_channel/monitoring_channel_public_key
+ cp ~/.ssh/id_rsa xos/observers/monitoring_channel/monitoring_channel_private_key
fi
sudo docker build -t xos .
@@ -34,6 +36,8 @@
# OpenStack is using port 8000...
MYIP=$( hostname -i )
+MYFLATLANIF=$( sudo bash -c "netstat -i" |grep flat|awk '{print $1}' )
+MYFLATLANIP=$( ifconfig $MYFLATLANIF | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}' )
sudo docker run -d --add-host="ctl:$MYIP" -p 9999:8000 $IMAGE
echo "Waiting for XOS to come up"
@@ -52,7 +56,17 @@
sudo chmod a+r /tmp/admin-openrc.sh
#sudo sed -i 's/:5000/:35357/' /tmp/admin-openrc.sh
source /tmp/admin-openrc.sh
-http --auth $AUTH POST $XOS/xos/controllers/ name=CloudLab deployment=$XOS/xos/deployments/1/ backend_type=OpenStack version=Juno auth_url=$OS_AUTH_URL admin_user=$OS_USERNAME admin_tenant=$OS_TENANT_NAME admin_password=$OS_PASSWORD domain=Default
+
+if [ "$CORD" -ne 1 ]
+then
+ http --auth $AUTH POST $XOS/xos/controllers/ name=CloudLab deployment=$XOS/xos/deployments/1/ backend_type=OpenStack version=Kilo auth_url=$OS_AUTH_URL admin_user=$OS_USERNAME admin_tenant=$OS_TENANT_NAME admin_password=$OS_PASSWORD domain=Default
+else
+ sudo cp /root/setup/settings /tmp
+ sudo chmod a+r /tmp/settings
+ source /tmp/settings
+ source /tmp/admin-openrc.sh
+ http --auth $AUTH POST $XOS/xos/controllers/ name=CloudLab deployment=$XOS/xos/deployments/1/ backend_type=OpenStack version=Kilo auth_url=$OS_AUTH_URL admin_user=$OS_USERNAME admin_tenant=$OS_TENANT_NAME admin_password=$OS_PASSWORD domain=Default rabbit_host=$MYFLATLANIP rabbit_user=$RABBIT_USER rabbit_password=$RABBIT_PASS
+fi
# Add controller to site
http --auth $AUTH PATCH $XOS/xos/sitedeployments/1/ controller=$XOS/xos/controllers/1/
@@ -78,5 +92,5 @@
if [ "$CORD" -ne 0 ]
then
DOCKER=$( sudo docker ps|grep $IMAGE|awk '{print $NF}' )
- sudo docker exec $DOCKER bash -c "cd /opt/xos/tosca; python run.py padmin@vicci.org samples/cord-cloudlab.yaml"
+ sudo docker exec $DOCKER bash -c "cd /opt/xos/tosca; python run.py padmin@vicci.org samples/cord-cloudlab.yaml; python run.py padmin@vicci.org samples/ceilometer.yaml"
fi
diff --git a/xos/apigen/modelgen b/xos/apigen/modelgen
index ec08c01..7f740d1 100644
--- a/xos/apigen/modelgen
+++ b/xos/apigen/modelgen
@@ -15,7 +15,6 @@
sys.path.append('.')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
from django.db.models.fields.related import ForeignKey, ManyToManyField
-from core.models import *
def singular(foo, keys):
for k in keys:
@@ -27,13 +26,24 @@
g = globals()
-def enum_classes():
- model_classes = []
- for c in g.values():
- if type(c)==type(PlCoreBase) and c.__name__ not in blacklist:
- model_classes.append(c)
- return model_classes
+def enum_classes(apps):
+ model_classes = []
+ for app in apps:
+ app = app + ".models"
+ models_module = __import__(app)
+ for part in app.split(".")[1:]:
+ if hasattr(models_module, "PlCoreBase"):
+ break
+ models_module = getattr(models_module,part)
+ PlCoreBase = getattr(models_module,"PlCoreBase")
+
+ for classname in dir(models_module):
+ c = getattr(models_module, classname, None)
+ if type(c)==type(PlCoreBase) and c.__name__ not in blacklist:
+ model_classes.append(c)
+
+ return model_classes
class GenObj(object):
def __str__(self):
@@ -146,15 +156,20 @@
def main():
- try:
- output = sys.argv[1]
- except:
- print 'Usage: modelgen <output template>'
- exit(1)
+ if len(sys.argv)==3:
+ apps = [x.strip() for x in sys.argv[1].split(",")]
+ output = sys.argv[2]
+ elif len(sys.argv)==2:
+ apps = ["core"]
+ output = sys.argv[1]
+ else:
+ print 'Usage: modelgen <output_template>'
+ print 'Alternative Usage: modelgen <app> <output_template>'
+ exit(1)
generator = Generator()
- models = enum_classes()
+ models = enum_classes(apps)
for m in models:
generator.add_object(m)
diff --git a/xos/ceilometer/models.py b/xos/ceilometer/models.py
index 50f921b..fbfecd3 100644
--- a/xos/ceilometer/models.py
+++ b/xos/ceilometer/models.py
@@ -27,6 +27,10 @@
KIND = CEILOMETER_KIND
+ sync_attributes = ("private_ip", "private_mac",
+ "ceilometer_ip", "ceilometer_mac",
+ "nat_ip", "nat_mac",)
+
default_attributes = {}
def __init__(self, *args, **kwargs):
ceilometer_services = CeilometerService.get_service_objects().all()
@@ -66,14 +70,30 @@
return addresses
@property
+ def nat_ip(self):
+ return self.addresses.get("nat", (None, None))[0]
+
+ @property
+ def nat_mac(self):
+ return self.addresses.get("nat", (None, None))[1]
+
+ @property
def private_ip(self):
return self.addresses.get("nat", (None, None))[0]
@property
+ def private_mac(self):
+ return self.addresses.get("nat", (None, None))[1]
+
+ @property
def ceilometer_ip(self):
return self.addresses.get("ceilometer", (None, None))[0]
@property
+ def ceilometer_mac(self):
+ return self.addresses.get("ceilometer", (None, None))[1]
+
+ @property
def site_tenant_list(self):
tenant_ids = Set()
for sp in SitePrivilege.objects.filter(user=self.creator):
@@ -109,7 +129,7 @@
def ceilometer_url(self):
if not self.ceilometer_ip:
return None
- return "http://" + self.ceilometer_ip + "/uri/to/ceilometer/api/"
+ return "http://" + self.private_ip + ":8888/"
def model_policy_monitoring_channel(pk):
# TODO: this should be made in to a real model_policy
diff --git a/xos/configurations/cord/Dockerfile.cord b/xos/configurations/cord/Dockerfile.cord
index dad9895..a21c7d0 100644
--- a/xos/configurations/cord/Dockerfile.cord
+++ b/xos/configurations/cord/Dockerfile.cord
@@ -5,7 +5,11 @@
ADD xos/configurations/common/id_rsa.pub /root/setup/padmin_public_key
ADD xos/configurations/common/id_rsa.pub /opt/xos/observers/vcpe/vcpe_public_key
ADD xos/configurations/common/id_rsa /opt/xos/observers/vcpe/vcpe_private_key
+ADD xos/configurations/common/id_rsa.pub /opt/xos/observers/onos/onos_key.pub
+ADD xos/configurations/common/id_rsa /opt/xos/observers/onos/onos_key
ADD xos/observers/vcpe/supervisor/vcpe-observer.conf /etc/supervisor/conf.d/
+ADD xos/observers/vbng/supervisor/vbng-observer.conf /etc/supervisor/conf.d/
+ADD xos/observers/onos/supervisor/onos-observer.conf /etc/supervisor/conf.d/
RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/vcpe/vcpe_observer_config
CMD /usr/bin/make -C /opt/xos/configurations/cord -f Makefile.inside; /bin/bash
diff --git a/xos/configurations/cord/Makefile.inside b/xos/configurations/cord/Makefile.inside
index a4bb5f1..a4494dc 100644
--- a/xos/configurations/cord/Makefile.inside
+++ b/xos/configurations/cord/Makefile.inside
@@ -6,5 +6,8 @@
python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab-nodes.yaml
python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord/cord.yaml
+setup_subscriber:
+ python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord/subscriber.yaml
+
run_develserver:
cd /opt/xos; python manage.py runserver 0.0.0.0:8000 --insecure
diff --git a/xos/configurations/cord/README b/xos/configurations/cord/README
deleted file mode 100644
index 7c80c0f..0000000
--- a/xos/configurations/cord/README
+++ /dev/null
@@ -1,4 +0,0 @@
-This configuration configures XOS with the CORD services. It is intended to be
-run on CloudLab, on the "ctl" node set up by the OpenStack profile. It launches
-an XOS container on Cloudlab that runs the XOS develserver. The container is
-left running in the background.
diff --git a/xos/configurations/cord/README.md b/xos/configurations/cord/README.md
new file mode 100644
index 0000000..5683601
--- /dev/null
+++ b/xos/configurations/cord/README.md
@@ -0,0 +1,97 @@
+# CORD development environment
+
+This configuration can be used to set up a CORD development environment.
+It does the following:
+
+* Sets up a basic dataplane for testing end-to-end packet flow between a subscriber client and the Internet
+* Brings up ONOS apps for controlling the dataplane: virtualbng, olt
+* Configures XOS with the CORD services: vCPE, vBNG, vOLT
+
+**NOTE:** This configuration is under **active development** and is not yet finished! Some features are not
+fully working yet.
+
+## End-to-end dataplane
+
+The configuration uses XOS to set up an end-to-end dataplane for development of the XOS services and ONOS apps
+used in CORD. It abstracts away most of the complexity of the CORD hardware using virtual networks
+and Open vSwitch (OvS) switches. At a high level the dataplane looks like this:
+
+```
+ olt virtualbng
+ ---- ----
+ ONOS ONOS
+ | |
+client ----> OvS ----> vCPE ----> OvS ----> Internet
+ 1 2 3 4
+```
+
+On the datapath are two OvS switches, controlled by the `olt` and `virtualbng` ONOS applications. Once all the pieces are in
+place, the client at left should be able to obtain an IP address via DHCP from the vCPE and send packets out to the Internet.
+
+All of the components in the above diagram (i.e., client, OvS switches, ONOS, and vCPE) currently run in distinct VMs
+created by XOS. The numbers in the diagram correspond to networks set up by XOS:
+
+1. subscriber_network
+2. lan_network
+3. wan_network
+4. public_network
+
+## How to run it
+
+The configuration is intended to be run on [CloudLab](http://cloudlab.us), on the *ctl* node set up by the OpenStack profile.
+It launches an XOS container on Cloudlab that runs the XOS develserver. The container is left running in the background.
+
+Running `make` in this directory creates the XOS Docker container and runs the TOSCA engine with `cord.yaml` to
+configure XOS with the CORD services. In addition, a number of VMs are created:
+
+1. *Slice mysite_onos*: runs the ONOS Docker container with `virtualbng` app loaded
+1. *Slice mysite_onos*: runs the ONOS Docker container with `olt` app loaded
+1. *Slice mysite_vbng*: for running OvS with the `virtualbng` app as controller
+1. *Slice mysite_volt*: for running OvS with the `olt` app as controller
+1. *Slice mysite_clients*: a subscriber client for end-to-end testing
+
+After the first VM is created (for running the `virtualbng` app) it is necessary to configure XOS's *service_vbng* with its URL.
+Log into XOS, click on *Services* tab at left, then *service_vbng* icon. Change **Vbng url:** to point to the IP address on
+`flat-lan-1-net` of the VM (it will start with 10.11).
+
+Once all the VMs are up and the ONOS apps are configured, XOS should be able to get an address mapping from the `virtualbng`
+ONOS app when creating a vCPE. To test this, enter the XOS Docker container and run:
+
+```
+$ cd /opt/xos/configurations/cord/
+$ make -f Makefile.inside setup_subscriber
+```
+
+This will run the TOSCA engine with `subscriber.yaml`. After a bit, a new VM should be created in slice *mysite_vcpe* running
+the vCPE Docker container. To verify that it has received an IP address mapping, look at the **Routeable subnet:** field in
+the appropriate *Vbng tenant* object in XOS. It should contain an IP address in the 10.254.0.0/24 subnet.
+
+## How to log into ONOS
+
+The ONOS Docker container runs in the VMs belonging to the *mysite_onos* slice. All ports exposed by the ONOS container are forwarded to the outside, and can be accessed from the *ctl* node using the `flat-lan-1-net` address of the hosting VM. For example, if the IP addresss of the VM is 10.11.10.30, then it is possible to SSH to ONOS as follows (password is *karaf*):
+
+```
+$ ssh -p 8101 karaf@10.11.10.30
+Password authentication
+Password:
+Welcome to Open Network Operating System (ONOS)!
+ ____ _ ______ ____
+ / __ \/ |/ / __ \/ __/
+ / /_/ / / /_/ /\ \
+ \____/_/|_/\____/___/
+
+
+Hit '<tab>' for a list of available commands
+and '[cmd] --help' for help on a specific command.
+Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown ONOS.
+
+onos>
+```
+
+For instance, to check the IP address mappings managed by the `virtualbng` app:
+
+```
+onos> vbngs
+ Private IP - Public IP
+ 10.0.1.3 - 10.254.0.129
+```
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 56ab11d..5b48398 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -7,6 +7,7 @@
topology_template:
node_templates:
+
# CORD Services
service_volt:
type: tosca.nodes.Service
@@ -18,55 +19,6 @@
view_url: /admin/cord/voltservice/$id$/
kind: vOLT
- Private:
- type: tosca.nodes.NetworkTemplate
-
- # networks required by vCPE
- lan_network:
- type: tosca.nodes.network.Network
- properties:
- ip_version: 4
- requirements:
- - network_template:
- node: Private
- relationship: tosca.relationships.UsesNetworkTemplate
- - owner:
- node: mysite_vcpe
- relationship: tosca.relationships.MemberOfSlice
- - connection:
- node: mysite_vcpe
- relationship: tosca.relationships.ConnectsToSlice
-
- wan_network:
- type: tosca.nodes.network.Network
- properties:
- ip_version: 4
- requirements:
- - network_template:
- node: Private
- relationship: tosca.relationships.UsesNetworkTemplate
- - owner:
- node: mysite_vcpe
- relationship: tosca.relationships.MemberOfSlice
- - connection:
- node: mysite_vcpe
- relationship: tosca.relationships.ConnectsToSlice
-
- hpc_client_network:
- type: tosca.nodes.network.Network
- properties:
- ip_version: 4
- requirements:
- - network_template:
- node: Private
- relationship: tosca.relationships.UsesNetworkTemplate
- - owner:
- node: mysite_vcpe
- relationship: tosca.relationships.MemberOfSlice
- - connection:
- node: mysite_vcpe
- relationship: tosca.relationships.ConnectsToSlice
-
service_vcpe:
type: tosca.nodes.VCPEService
requirements:
@@ -84,11 +36,148 @@
type: tosca.nodes.VBNGService
properties:
view_url: /admin/cord/vbngservice/$id$/
- vbng_url: http://10.0.3.136:8181/onos/virtualbng/
+# if unspecified, vbng observer will look for an ONOSApp Tenant and
+# generate a URL from its IP address
+# vbng_url: http://10.11.10.24:8181/onos/virtualbng/
+
+ service_ONOS:
+ type: tosca.nodes.ONOSService
+ requirements:
+ properties:
+ kind: onos
+ view_url: /admin/onos/onosservice/$id$/
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ artifacts:
+ pubkey: /opt/xos/observers/onos/onos_key.pub
+
+ vBNG_ONOS_app:
+ type: tosca.nodes.ONOSvBNGApp
+ requirements:
+ - onos_tenant:
+ node: service_ONOS
+ relationship: tosca.relationships.TenantOfService
+ - vbng_service:
+ node: service_vbng
+ relationship: tosca.relationships.UsedByService
+ properties:
+ dependencies: org.onosproject.proxyarp, org.onosproject.virtualbng, org.onosproject.openflow, org.onosproject.fwd
+ config_addresses.json: >
+ {
+ "addresses" : [
+ {
+ "dpid" : "00:00:00:00:00:00:00:a1",
+ "port" : "1",
+ "ips" : [10.0.1.253/24"],
+ "mac" : "00:00:00:00:00:99"
+
+ },
+ {
+ "dpid" : "00:00:00:00:00:00:00:a5",
+ "port" : "2",
+ "ips" : ["10.254.0.1/24"],
+ "mac" : "00:00:00:00:00:98"
+ }
+ ]
+ }
+ config_virtualbng.json: >
+ {
+ "localPublicIpPrefixes" : [
+ "10.254.0.128/25"
+ ],
+ "nextHopIpAddress" : "10.254.0.1",
+ "publicFacingMac" : "00:00:00:00:00:66",
+ "xosIpAddress" : "10.11.10.1",
+ "xosRestPort" : "9999"
+ }
+
+
+ # Network templates
+ Private:
+ type: tosca.nodes.NetworkTemplate
+
+ Public network hack:
+ type: tosca.nodes.NetworkTemplate
+ properties:
+ visibility: private
+ translation: NAT
+ shared_network_name: tun0-net
+
+
+ # Networks required by the CORD setup
+ lan_network:
+ type: tosca.nodes.network.Network
+ properties:
+ ip_version: 4
+ requirements:
+ - network_template:
+ node: Private
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_vcpe
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_vcpe
+ relationship: tosca.relationships.ConnectsToSlice
+ - connection:
+ node: mysite_volt
+ relationship: tosca.relationships.ConnectsToSlice
+
+ wan_network:
+ type: tosca.nodes.network.Network
+ properties:
+ ip_version: 4
+ requirements:
+ - network_template:
+ node: Private
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_vcpe
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_vcpe
+ relationship: tosca.relationships.ConnectsToSlice
+ - connection:
+ node: mysite_vbng
+ relationship: tosca.relationships.ConnectsToSlice
+
+ subscriber_network:
+ type: tosca.nodes.network.Network
+ properties:
+ ip_version: 4
+ requirements:
+ - network_template:
+ node: Private
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_volt
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_volt
+ relationship: tosca.relationships.ConnectsToSlice
+ - connection:
+ node: mysite_clients
+ relationship: tosca.relationships.ConnectsToSlice
+
+ public_network:
+ type: tosca.nodes.network.Network
+ properties:
+ requirements:
+ - network_template:
+ node: Public network hack
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_vbng
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_vbng
+ relationship: tosca.relationships.ConnectsToSlice
+
mysite:
type: tosca.nodes.Site
+
+ # CORD Slices
mysite_vcpe:
description: vCPE Controller Slice
type: tosca.nodes.Slice
@@ -100,66 +189,153 @@
node: mysite
relationship: tosca.relationships.MemberOfSite
- # Now let's add a subscriber
+ mysite_onos:
+ description: ONOS Controller Slice
+ type: tosca.nodes.Slice
+ requirements:
+ - ONOS:
+ node: service_ONOS
+ relationship: tosca.relationships.MemberOfService
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
- My House:
- type: tosca.nodes.CORDSubscriber
- properties:
- service_specific_id: 1234
- firewall_enable: false
- cdn_enable: false
- url_filter_enable: false
- url_filter_level: R
+ mysite_vbng:
+ description: slice running OVS controlled by vBNG
+ type: tosca.nodes.Slice
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
- Mom's PC:
- type: tosca.nodes.CORDUser
- properties:
- mac: 010203040506
- level: PG_13
- requirements:
- - household:
- node: My House
- relationship: tosca.relationships.SubscriberDevice
+ mysite_volt:
+ description: OVS controlled by vOLT
+ type: tosca.nodes.Slice
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
- Dad's PC:
- type: tosca.nodes.CORDUser
- properties:
- mac: 90E2Ba82F975
- level: PG_13
- requirements:
- - household:
- node: My House
- relationship: tosca.relationships.SubscriberDevice
+ mysite_clients:
+ description: slice for clients at the subscriber
+ type: tosca.nodes.Slice
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
- Jack's Laptop:
- type: tosca.nodes.CORDUser
- properties:
- mac: 685B359D91D5
- level: PG_13
- requirements:
- - household:
- node: My House
- relationship: tosca.relationships.SubscriberDevice
- Jill's Laptop:
- type: tosca.nodes.CORDUser
- properties:
- mac: 34363BC9B6A6
- level: PG_13
- requirements:
- - household:
- node: My House
- relationship: tosca.relationships.SubscriberDevice
+ # Virtual machines
+ onos_app_1:
+ 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_onos
+ relationship: tosca.relationships.MemberOfSlice
- My Volt:
- type: tosca.nodes.VOLTTenant
- properties:
- service_specific_id: 1234
- vlan_id: 4321
- requirements:
- - provider_service:
- node: service_volt
- relationship: tosca.relationships.MemberOfService
- - subscriber:
- node: My House
- relationship: tosca.relationships.BelongsToSubscriber
+ onos_app_2:
+ 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_onos
+ relationship: tosca.relationships.MemberOfSlice
+
+ # VM for running the OVS controlled by vBNG
+ ovs_vbng:
+ 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.04
+ requirements:
+ - slice:
+ node: mysite_vbng
+ relationship: tosca.relationships.MemberOfSlice
+
+ # VM for running the OVS controlled by vOLT
+ ovs_volt:
+ 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.04
+ requirements:
+ - slice:
+ node: mysite_volt
+ relationship: tosca.relationships.MemberOfSlice
+
+ # A subscriber client VM
+ client1:
+ 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.04
+ requirements:
+ - slice:
+ node: mysite_clients
+ relationship: tosca.relationships.MemberOfSlice
+
diff --git a/xos/configurations/cord/subscriber.yaml b/xos/configurations/cord/subscriber.yaml
new file mode 100644
index 0000000..f77f4d5
--- /dev/null
+++ b/xos/configurations/cord/subscriber.yaml
@@ -0,0 +1,82 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Set up a subscriber for CORD
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+
+ # These services defined in cord.yaml
+ service_volt:
+ type: tosca.nodes.Service
+
+ service_vcpe:
+ type: tosca.nodes.VCPEService
+
+ service_vbng:
+ type: tosca.nodes.VBNGService
+
+ # A subscriber
+ My House:
+ type: tosca.nodes.CORDSubscriber
+ properties:
+ service_specific_id: 123
+ firewall_enable: false
+ cdn_enable: false
+ url_filter_enable: false
+ url_filter_level: R
+
+ Mom's PC:
+ type: tosca.nodes.CORDUser
+ properties:
+ mac: 010203040506
+ level: PG_13
+ requirements:
+ - household:
+ node: My House
+ relationship: tosca.relationships.SubscriberDevice
+
+ Dad's PC:
+ type: tosca.nodes.CORDUser
+ properties:
+ mac: 90E2Ba82F975
+ level: PG_13
+ requirements:
+ - household:
+ node: My House
+ relationship: tosca.relationships.SubscriberDevice
+
+ Jack's Laptop:
+ type: tosca.nodes.CORDUser
+ properties:
+ mac: 685B359D91D5
+ level: PG_13
+ requirements:
+ - household:
+ node: My House
+ relationship: tosca.relationships.SubscriberDevice
+
+ Jill's Laptop:
+ type: tosca.nodes.CORDUser
+ properties:
+ mac: 34363BC9B6A6
+ level: PG_13
+ requirements:
+ - household:
+ node: My House
+ relationship: tosca.relationships.SubscriberDevice
+
+ My Volt:
+ type: tosca.nodes.VOLTTenant
+ properties:
+ service_specific_id: 123
+ vlan_id: 432
+ requirements:
+ - provider_service:
+ node: service_volt
+ relationship: tosca.relationships.MemberOfService
+ - subscriber:
+ node: My House
+ relationship: tosca.relationships.BelongsToSubscriber
diff --git a/xos/configurations/opencloud/Dockerfile b/xos/configurations/opencloud/Dockerfile
index ad85718..5b09507 100644
--- a/xos/configurations/opencloud/Dockerfile
+++ b/xos/configurations/opencloud/Dockerfile
@@ -31,8 +31,8 @@
libyaml-dev \
pkg-config \
supervisor \
- python-crypto
- python-httplib2 \
+ python-crypto \
+ python-httplib2>=0.9.1 \
python-jinja2 \
python-paramiko \
python-pip \
@@ -47,7 +47,7 @@
python-ceilometerclient
RUN pip install \
- django==1.7
+ django==1.7 \
djangorestframework==2.4.4 \
markdown \
pyyaml \
@@ -69,13 +69,14 @@
pygraphviz \
dnslib
+RUN easy_install --upgrade httplib2
+
RUN easy_install \
- django_evolution
+ django_evolution \
python_gflags \
google_api_python_client \
httplib2.ca_certs_locater
-RUN easy_install --upgrade httplib2
# Install custom Ansible
RUN git clone -b release1.8.2 git://github.com/ansible/ansible.git /opt/ansible
@@ -103,7 +104,7 @@
ADD observer.conf /etc/supervisor/conf.d/
# Get XOS
-ADD xos /opt/xos
+RUN git clone git://github.com/open-cloud/xos.git /tmp/xos && mv /tmp/xos/xos /opt/
# Initscript is broken in Ubuntu
#ADD observer-initscript /etc/init.d/xosobserver
@@ -134,7 +135,6 @@
RUN apt-get install -y m4
RUN pip install python-dateutil
RUN bash /opt/xos/tosca/install_tosca.sh
-RUN /usr/bin/python /opt/xos/tosca/run.py padmin@vicci.org ${TOSCA_CONFIG_PATH}
EXPOSE 8000
diff --git a/xos/configurations/opencloud/Makefile b/xos/configurations/opencloud/Makefile
index aba30dc..863f2b7 100644
--- a/xos/configurations/opencloud/Makefile
+++ b/xos/configurations/opencloud/Makefile
@@ -1,8 +1,13 @@
+RUNNING_CONTAINER:=$(shell sudo docker ps|grep "opencloud_server"|awk '{print $$NF}')
+
.PHONY: build
build: ; docker build --rm -t opencloud .
.PHONY: run
-run: ; docker run --rm -d -e TOSCA_CONFIG_PATH=$TOSCA_CONFIG_PATH --name opencloud_server opencloud
+run: ; docker run --rm --name opencloud_server opencloud
+
+.PHONY: runtosca
+runtosca: ; docker exec -it $RUNNING_CONTAINER /usr/bin/python /opt/xos/tosca/run.py padmin@vicci.org $TOSCA_CONFIG_PATH
.PHONY: stop
stop: ; docker stop opencloud_server
diff --git a/xos/configurations/opencloud/ansible-hosts b/xos/configurations/opencloud/ansible-hosts
new file mode 100644
index 0000000..0dd74f1
--- /dev/null
+++ b/xos/configurations/opencloud/ansible-hosts
@@ -0,0 +1,2 @@
+[localhost]
+127.0.0.1
diff --git a/xos/configurations/opencloud/observer.conf b/xos/configurations/opencloud/observer.conf
new file mode 100644
index 0000000..92545eb
--- /dev/null
+++ b/xos/configurations/opencloud/observer.conf
@@ -0,0 +1,2 @@
+[program:observer]
+command=python /opt/xos/xos-observer.py
diff --git a/xos/cord/models.py b/xos/cord/models.py
index d55ff2d..26d7d24 100644
--- a/xos/cord/models.py
+++ b/xos/cord/models.py
@@ -689,7 +689,7 @@
class VBNGService(Service):
KIND = VBNG_KIND
- simple_attributes = ( ("vbng_url", "http://10.0.3.136:8181/onos/virtualbng/"), )
+ simple_attributes = ( ("vbng_url", ""), ) # "http://10.0.3.136:8181/onos/virtualbng/"
class Meta:
app_label = "cord"
diff --git a/xos/core/admin.py b/xos/core/admin.py
index b3dc67d..a5b89be 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -757,7 +757,7 @@
class ControllerAdmin(XOSBaseAdmin):
model = Controller
- fieldList = ['deployment', 'name', 'backend_type', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password', 'domain']
+ fieldList = ['deployment', 'name', 'backend_type', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password', 'domain', 'rabbit_host', 'rabbit_user', 'rabbit_password']
fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
inlines = [ControllerSiteInline] # ,ControllerImagesInline]
list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
@@ -787,6 +787,23 @@
return tabs
+class TenantAttributeAdmin(XOSBaseAdmin):
+ model = TenantAttribute
+ list_display = ('backend_status_icon', 'tenant', 'name', 'value')
+ list_display_links = ('backend_status_icon', 'name')
+ fieldList = ('backend_status_text', 'tenant', 'name', 'value', )
+ fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
+ readonly_fields = ('backend_status_text', )
+
+ suit_form_tabs =(('general', 'Tenant Root Details'),
+ )
+
+class TenantAttrAsTabInline(XOSTabularInline):
+ model = TenantAttribute
+ fields = ['name','value']
+ extra = 0
+ suit_classes = 'suit-tab suit-tab-tenantattrs'
+
class TenantRootRoleAdmin(XOSBaseAdmin):
model = TenantRootRole
fields = ('role',)
@@ -2006,4 +2023,5 @@
admin.site.register(Flavor, FlavorAdmin)
admin.site.register(TenantRoot, TenantRootAdmin)
admin.site.register(TenantRootRole, TenantRootRoleAdmin)
+ admin.site.register(TenantAttribute, TenantAttributeAdmin)
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index ad271a4..c380e9c 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -2,7 +2,7 @@
from .project import Project
from .singletonmodel import SingletonModel
from .service import Service, Tenant, TenantWithContainer, CoarseTenant, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, Subscriber, Provider
-from .service import ServiceAttribute
+from .service import ServiceAttribute, TenantAttribute
from .tag import Tag
from .role import Role
from .site import Site, Deployment, DeploymentRole, DeploymentPrivilege, Controller, ControllerRole, ControllerSite, SiteDeployment
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
index 6240c34..75826f6 100644
--- a/xos/core/models/instance.py
+++ b/xos/core/models/instance.py
@@ -158,6 +158,17 @@
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):
+ return self.get_network_ip("nat")
+
@staticmethod
def select_by_user(user):
if user.is_admin:
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 9530c72..950ce02 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -67,6 +67,10 @@
return cls.objects.filter(kind = cls.KIND)
@classmethod
+ def get_deleted_service_objects(cls):
+ return cls.deleted_objects.filter(kind = cls.KIND)
+
+ @classmethod
def get_service_objects_by_user(cls, user):
return cls.select_by_user(user).filter(kind = cls.KIND)
@@ -488,6 +492,11 @@
KIND = "Provider"
+class TenantAttribute(PlCoreBase):
+ name = models.CharField(help_text="Attribute Name", max_length=128)
+ value = models.TextField(help_text="Attribute Value")
+ tenant = models.ForeignKey(Tenant, related_name='tenantattributes', help_text="The Tenant this attribute is associated with")
+
class TenantRootRole(PlCoreBase):
ROLE_CHOICES = (('admin','Admin'), ('access','Access'))
diff --git a/xos/core/models/site.py b/xos/core/models/site.py
index 26ff191..1bdef36 100644
--- a/xos/core/models/site.py
+++ b/xos/core/models/site.py
@@ -263,6 +263,9 @@
admin_password = StrippedCharField(max_length=200, null=True, blank=True, help_text="Password of theadmin user at this controller")
admin_tenant = StrippedCharField(max_length=200, null=True, blank=True, help_text="Name of the tenant the admin user belongs to")
domain = StrippedCharField(max_length=200, null=True, blank=True, help_text="Name of the domain this controller belongs to")
+ rabbit_host = StrippedCharField(max_length=200, null=True, blank=True, help_text="IP address of rabbitmq server at this controller")
+ rabbit_user = StrippedCharField(max_length=200, null=True, blank=True, help_text="Username of rabbitmq server at this controller")
+ rabbit_password = StrippedCharField(max_length=200, null=True, blank=True, help_text="Password of rabbitmq server at this controller")
deployment = models.ForeignKey(Deployment,related_name='controllerdeployments')
def __init__(self, *args, **kwargs):
diff --git a/xos/core/xoslib/methods/monitoringchannel.py b/xos/core/xoslib/methods/monitoringchannel.py
new file mode 100644
index 0000000..baab346
--- /dev/null
+++ b/xos/core/xoslib/methods/monitoringchannel.py
@@ -0,0 +1,89 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from core.models import *
+from django.forms import widgets
+from ceilometer.models import MonitoringChannel, CeilometerService
+from plus import PlusSerializerMixin
+from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
+
+if hasattr(serializers, "ReadOnlyField"):
+ # rest_framework 3.x
+ ReadOnlyField = serializers.ReadOnlyField
+else:
+ # rest_framework 2.x
+ ReadOnlyField = serializers.Field
+
+def get_default_ceilometer_service():
+ ceilometer_services = CeilometerService.get_service_objects().all()
+ if ceilometer_services:
+ return ceilometer_services[0].id
+ return None
+
+class MonitoringChannelSerializer(serializers.ModelSerializer, PlusSerializerMixin):
+ id = ReadOnlyField()
+ service_specific_attribute = ReadOnlyField()
+ ceilometer_url = ReadOnlyField()
+ tenant_list_str = ReadOnlyField()
+ creator = ReadOnlyField()
+ instance = ReadOnlyField()
+ provider_service = serializers.PrimaryKeyRelatedField(queryset=CeilometerService.get_service_objects().all(), default=get_default_ceilometer_service)
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+
+ computeNodeName = serializers.SerializerMethodField("getComputeNodeName")
+
+ class Meta:
+ model = MonitoringChannel
+ fields = ('humanReadableName', 'id', 'provider_service', 'service_specific_attribute', 'ceilometer_url', 'tenant_list_str', 'creator', 'instance', 'computeNodeName' )
+
+ def getHumanReadableName(self, obj):
+ return obj.__unicode__()
+
+ def getComputeNodeName(self, obj):
+ instance = obj.instance
+ if not instance:
+ return None
+ return instance.node.name
+
+class MonitoringChannelList(XOSListCreateAPIView):
+ serializer_class = MonitoringChannelSerializer
+
+ method_kind = "list"
+ method_name = "monitoringchannel"
+
+ def get_queryset(self):
+ queryset = MonitoringChannel.get_tenant_objects().select_related().all()
+
+ current_user = self.request.user.username
+ if current_user is not None:
+ ids = [x.id for x in queryset if x.creator.username==current_user]
+ queryset = queryset.filter(id__in=ids)
+
+ return queryset
+
+ def post(self, request, format=None):
+ current_user = request.user.username
+ existing_obj = None
+ for obj in MonitoringChannel.get_tenant_objects().all():
+ if (obj.creator.username == current_user):
+ existing_obj = obj
+ break
+
+ if existing_obj:
+ serializer = MonitoringChannelSerializer(existing_obj)
+ headers = self.get_success_headers(serializer.data)
+ return Response( serializer.data, status=status.HTTP_200_OK )
+
+ return super(MonitoringChannelList, self).post(request, format)
+
+class MonitoringChannelDetail(XOSRetrieveUpdateDestroyAPIView):
+ serializer_class = MonitoringChannelSerializer
+ queryset = MonitoringChannel.get_tenant_objects().select_related().all()
+
+ method_kind = "detail"
+ method_name = "monitoringchannel"
+
diff --git a/xos/dmdot b/xos/dmdot
index 8570c4d..124c7cf 100755
--- a/xos/dmdot
+++ b/xos/dmdot
@@ -12,7 +12,7 @@
from django.db.models.fields.related import ForeignKey
# defaults
-apps = ["core", "hpc", "cord", "requestrouter"]
+apps = ["core", "hpc", "cord", "requestrouter", "services.onos"]
output = "-json"
# syntax: dmdot [-json | -dot] [app_name]
@@ -22,7 +22,7 @@
if arg.startswith("-"):
output = arg
else:
- app = arg
+ apps = [arg]
model_classes = []
class_names = []
diff --git a/xos/helloworld/view.py b/xos/helloworld/view.py
new file mode 100644
index 0000000..b3eec29
--- /dev/null
+++ b/xos/helloworld/view.py
@@ -0,0 +1,58 @@
+from django.http import HttpResponse
+from django.views.generic import TemplateView, View
+from django import template
+from monitor import driver
+from core.models import *
+from helloworld.models import *
+import json
+import os
+import time
+import tempfile
+
+class HelloWorldView(TemplateView):
+ head_template = r"""{% extends "admin/dashboard/dashboard_base.html" %}
+ {% load admin_static %}
+ {% block content %}
+ """
+
+ tail_template = r"{% endblock %}"
+
+ def get(self, request, name="root", *args, **kwargs):
+ head_template = self.head_template
+ tail_template = self.tail_template
+
+ try:
+ hello_name = request.GET['hello_name']
+ world_name = request.GET['world_name']
+ instance_id_str = request.GET['instance_id']
+ instance_id = int(instance_id_str)
+
+ i = Instance.objects.get(pk=instance_id)
+ i.pk=None
+ i.userData=None
+ i.instance_id=None
+ i.instance_name=None
+ i.enacted=None
+ i.save()
+ h = Hello(name=hello_name,sliver_backref=i)
+ w = World(hello=h,name=world_name)
+ h.save()
+ w.save()
+
+ t = template.Template(head_template + 'Done. New instance id: %r'%i.pk + self.tail_template)
+ except KeyError:
+ html = """<form>
+ Hello string: <input type="text" name="hello_name" placeholder="Planet"><br>
+ World string: <input type="text" name="world_name" placeholder="Earth"><br>
+ Id of instance to copy: <input type="text" name="instance_id" placeholder="3"><br>
+ <input type="submit" value="Submit">
+ </form>"""
+
+ t = template.Template(head_template + html + self.tail_template)
+
+ response_kwargs = {}
+ response_kwargs.setdefault('content_type', self.content_type)
+ return self.response_class(
+ request = request,
+ template = t,
+ **response_kwargs)
diff --git a/xos/observers/base/SyncInstanceUsingAnsible.py b/xos/observers/base/SyncInstanceUsingAnsible.py
index 9455780..901bc97 100644
--- a/xos/observers/base/SyncInstanceUsingAnsible.py
+++ b/xos/observers/base/SyncInstanceUsingAnsible.py
@@ -8,7 +8,7 @@
from xos.config import Config
from observer.syncstep import SyncStep
from observer.ansible import run_template_ssh
-from core.models import Service, Slice
+from core.models import Service, Slice, ControllerSlice, ControllerUser
from util.logger import Logger, logging
logger = Logger(level=logging.INFO)
@@ -58,25 +58,59 @@
def sync_fields(self, o, fields):
self.run_playbook(o, fields)
+ def prepare_record(self, o):
+ pass
+
def sync_record(self, o):
logger.info("sync'ing object %s" % str(o))
- instance = self.get_instance(o)
- if not instance:
- self.defer_sync(o, "waiting on instance")
- return
-
if not os.path.exists(self.service_key_name):
raise Exception("Service key %s does not exist" % self.service_key_name)
service_key = file(self.service_key_name).read()
- fields = { "instance_name": instance.name,
- "hostname": instance.node.name,
- "instance_id": instance.instance_id,
- "private_key": service_key,
- "ansible_tag": "vcpe_tenant_" + str(o.id)
- }
+ self.prepare_record(o)
+
+ instance = self.get_instance(o)
+
+ if isinstance(instance, basestring):
+ # sync to some external host
+
+ # XXX - this probably needs more work...
+
+ fields = { "hostname": instance,
+ "instance_id": "ubuntu", # this is the username to log into
+ "private_key": service.key,
+ }
+ else:
+ # sync to an XOS instance
+ if not instance:
+ self.defer_sync(o, "waiting on instance")
+ return
+
+ if not instance.instance_name:
+ self.defer_sync(o, "waiting on instance.instance_name")
+ return
+
+ cslice = ControllerSlice.objects.get(slice=instance.slice)
+ if not cslice:
+ raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
+
+ cuser = ControllerUser.objects.get(user=instance.creator)
+ if not cuser:
+ raise Exception("Controller user object for %s does not exist" % instance.creator)
+
+ fields = { "instance_name": instance.name,
+ "hostname": instance.node.name,
+ "instance_id": instance.instance_id,
+ "private_key": service_key,
+ "keystone_tenant_id": cslice.tenant_id,
+ "keystone_user_id": cuser.kuser_id,
+ "rabbit_user": instance.controller.rabbit_user,
+ "rabbit_password": instance.controller.rabbit_password,
+ "rabbit_host": instance.controller.rabbit_host,
+ "ansible_tag": o.__class__.__name__ + "_" + str(o.id)
+ }
# If 'o' defines a 'sync_attributes' list, then we'll copy those
# attributes into the Ansible recipe's field list automatically.
diff --git a/xos/observers/hello_world/run.sh b/xos/observers/hello_world/run.sh
deleted file mode 100755
index 1107a7a..0000000
--- a/xos/observers/hello_world/run.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#if [[ ! -e ./hpc-backend.py ]]; then
-# ln -s ../xos-observer.py hpc-backend.py
-#fi
-
-export XOS_DIR=/opt/xos
-python helloworld-observer.py -C $XOS_DIR/observers/hello_world/helloworld_config
diff --git a/xos/observers/hello_world/helloworld-observer.py b/xos/observers/helloworld/helloworld-observer.py
similarity index 100%
rename from xos/observers/hello_world/helloworld-observer.py
rename to xos/observers/helloworld/helloworld-observer.py
diff --git a/xos/observers/hello_world/helloworld_config b/xos/observers/helloworld/helloworld_config
similarity index 94%
rename from xos/observers/hello_world/helloworld_config
rename to xos/observers/helloworld/helloworld_config
index 97248ae..671af51 100644
--- a/xos/observers/hello_world/helloworld_config
+++ b/xos/observers/helloworld/helloworld_config
@@ -37,7 +37,7 @@
images_directory=/opt/xos/images
dependency_graph=/opt/xos/model-deps
logfile=/var/log/xos_backend.log
-steps_dir=/opt/xos/observers/hello_world/steps
+steps_dir=/opt/xos/observers/helloworld/steps
[gui]
disable_minidashboard=True
diff --git a/xos/observers/hello_world/model-deps b/xos/observers/helloworld/model-deps
similarity index 100%
rename from xos/observers/hello_world/model-deps
rename to xos/observers/helloworld/model-deps
diff --git a/xos/observers/hello_world/nohup.out b/xos/observers/helloworld/nohup.out
similarity index 100%
rename from xos/observers/hello_world/nohup.out
rename to xos/observers/helloworld/nohup.out
diff --git a/xos/observers/helloworld/run.sh b/xos/observers/helloworld/run.sh
new file mode 100755
index 0000000..f56ffe3
--- /dev/null
+++ b/xos/observers/helloworld/run.sh
@@ -0,0 +1,6 @@
+#if [[ ! -e ./hpc-backend.py ]]; then
+# ln -s ../xos-observer.py hpc-backend.py
+#fi
+
+export XOS_DIR=/opt/xos
+python helloworld-observer.py -C $XOS_DIR/observers/helloworld/helloworld_config
diff --git a/xos/observers/hello_world/start.sh b/xos/observers/helloworld/start.sh
similarity index 100%
rename from xos/observers/hello_world/start.sh
rename to xos/observers/helloworld/start.sh
diff --git a/xos/observers/hello_world/steps/sync_hello.py b/xos/observers/helloworld/steps/sync_hello.py
similarity index 65%
rename from xos/observers/hello_world/steps/sync_hello.py
rename to xos/observers/helloworld/steps/sync_hello.py
index f59ec5c..1fb8c2b 100644
--- a/xos/observers/hello_world/steps/sync_hello.py
+++ b/xos/observers/helloworld/steps/sync_hello.py
@@ -18,7 +18,9 @@
requested_interval=0
def sync_record(self, record):
- open('/tmp/hello-synchronizer','w').write(record.name)
+ instance = record.sliver_backref
+ instance.userData="packages:\n - apache2\nruncmd:\n - update-rc.d apache2 enable\n - service apache2 start\nwrite_files:\n- content: Hello %s\n path: /var/www/html/hello.txt"%record.name
+ instance.save()
def delete_record(self, m):
return
diff --git a/xos/observers/hello_world/steps/sync_world.py b/xos/observers/helloworld/steps/sync_world.py
similarity index 100%
rename from xos/observers/hello_world/steps/sync_world.py
rename to xos/observers/helloworld/steps/sync_world.py
diff --git a/xos/observers/hello_world/stop.sh b/xos/observers/helloworld/stop.sh
similarity index 100%
rename from xos/observers/hello_world/stop.sh
rename to xos/observers/helloworld/stop.sh
diff --git a/xos/observers/monitoring_channel/files/docker.list b/xos/observers/monitoring_channel/files/docker.list
new file mode 100644
index 0000000..0ee9ae0
--- /dev/null
+++ b/xos/observers/monitoring_channel/files/docker.list
@@ -0,0 +1 @@
+deb https://get.docker.com/ubuntu docker main
diff --git a/xos/observers/monitoring_channel/files/vm-resolv.conf b/xos/observers/monitoring_channel/files/vm-resolv.conf
new file mode 100644
index 0000000..cae093a
--- /dev/null
+++ b/xos/observers/monitoring_channel/files/vm-resolv.conf
@@ -0,0 +1 @@
+nameserver 8.8.8.8
diff --git a/xos/observers/monitoring_channel/model-deps b/xos/observers/monitoring_channel/model-deps
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/xos/observers/monitoring_channel/model-deps
@@ -0,0 +1 @@
+{}
diff --git a/xos/observers/hello_world/helloworld-observer.py b/xos/observers/monitoring_channel/monitoring_channel_observer.py
similarity index 100%
copy from xos/observers/hello_world/helloworld-observer.py
copy to xos/observers/monitoring_channel/monitoring_channel_observer.py
diff --git a/xos/observers/monitoring_channel/monitoring_channel_observer_config b/xos/observers/monitoring_channel/monitoring_channel_observer_config
new file mode 100644
index 0000000..922a019
--- /dev/null
+++ b/xos/observers/monitoring_channel/monitoring_channel_observer_config
@@ -0,0 +1,40 @@
+
+[plc]
+name=plc
+deployment=VICCI
+
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+host=128.112.171.237
+port=8000
+ssl_key=None
+ssl_cert=None
+ca_ssl_cert=None
+ratelimit_enabled=0
+omf_enabled=0
+mail_support_address=support@localhost
+nova_enabled=True
+
+[observer]
+name=monitoring_channel
+dependency_graph=/opt/xos/observers/monitoring_channel/model-deps
+steps_dir=/opt/xos/observers/monitoring_channel/steps
+sys_dir=/opt/xos/observers/monitoring_channel/sys
+deleters_dir=/opt/xos/observers/monitoring_channel/deleters
+log_file=console
+driver=None
+pretend=False
+backoff_disabled=True
+save_ansible_output=True
+proxy_ssh=True
+full_setup=True
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/observers/monitoring_channel/steps/sync_monitoringchannel.py b/xos/observers/monitoring_channel/steps/sync_monitoringchannel.py
new file mode 100644
index 0000000..919b4ea
--- /dev/null
+++ b/xos/observers/monitoring_channel/steps/sync_monitoringchannel.py
@@ -0,0 +1,74 @@
+import hashlib
+import os
+import socket
+import sys
+import base64
+import time
+from django.db.models import F, Q
+from xos.config import Config
+from observer.syncstep import SyncStep
+from observer.ansible import run_template_ssh
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+from core.models import Service, Slice
+from ceilometer.models import MonitoringChannel
+from util.logger import Logger, logging
+
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+logger = Logger(level=logging.INFO)
+
+class SyncMonitoringChannel(SyncInstanceUsingAnsible):
+ provides=[MonitoringChannel]
+ observes=MonitoringChannel
+ requested_interval=0
+ template_name = "sync_monitoringchannel.yaml"
+ service_key_name = "/opt/xos/observers/monitoring_channel/monitoring_channel_private_key"
+
+ def __init__(self, *args, **kwargs):
+ super(SyncMonitoringChannel, self).__init__(*args, **kwargs)
+
+ def fetch_pending(self, deleted):
+ if (not deleted):
+ objs = MonitoringChannel.get_tenant_objects().filter(Q(enacted__lt=F('updated')) | Q(enacted=None),Q(lazy_blocked=False))
+ else:
+ objs = MonitoringChannel.get_deleted_tenant_objects()
+
+ return objs
+
+ def get_extra_attributes(self, o):
+ # This is a place to include extra attributes. In the case of Monitoring Channel, we need to know
+ # 1) Allowed tenant ids
+ # 2) Ceilometer API service endpoint URL if running externally
+ # 3) Credentials to access Ceilometer API service
+
+ instance = self.get_instance(o)
+
+ try:
+ full_setup = Config().observer_full_setup
+ except:
+ full_setup = True
+
+ fields = {"unique_id": o.id,
+ "allowed_tenant_ids": o.tenant_list,
+ "auth_url":instance.controller.auth_url,
+ "admin_user":instance.controller.admin_user,
+ "admin_password":instance.controller.admin_password,
+ "admin_tenant":instance.controller.admin_tenant,
+ "full_setup": full_setup}
+
+ return fields
+
+ def run_playbook(self, o, fields):
+ #ansible_hash = hashlib.md5(repr(sorted(fields.items()))).hexdigest()
+ #quick_update = (o.last_ansible_hash == ansible_hash)
+
+ #if quick_update:
+ # logger.info("quick_update triggered; skipping ansible recipe")
+ #else:
+ super(SyncMonitoringChannel, self).run_playbook(o, fields)
+
+ #o.last_ansible_hash = ansible_hash
+
+ def delete_record(self, m):
+ pass
diff --git a/xos/observers/monitoring_channel/steps/sync_monitoringchannel.yaml b/xos/observers/monitoring_channel/steps/sync_monitoringchannel.yaml
new file mode 100644
index 0000000..bbe284f
--- /dev/null
+++ b/xos/observers/monitoring_channel/steps/sync_monitoringchannel.yaml
@@ -0,0 +1,89 @@
+---
+- hosts: {{ instance_name }}
+ gather_facts: False
+ connection: ssh
+ user: ubuntu
+ sudo: yes
+ vars:
+ unique_id: {{ unique_id }}
+ auth_url: {{ auth_url }}
+ admin_user: {{ admin_user }}
+ admin_password: {{ admin_password }}
+ admin_tenant: {{ admin_tenant }}
+ shared_lan_ip: {{ private_ip }}
+ shared_lan_mac: {{ private_mac }}
+ headnode_flat_lan_ip: {{ rabbit_host }}
+ ceilometer_client_acess_ip: {{ ceilometer_ip }}
+ ceilometer_client_acess_mac: {{ ceilometer_mac }}
+ allowed_tenant_ids:
+ {% for allowed_tenant_id in allowed_tenant_ids %}
+ - {{ allowed_tenant_id }}
+ {% endfor %}
+
+ tasks:
+{% if full_setup %}
+ - name: Docker repository
+ copy: src=/opt/xos/observers/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-1.5.0 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/observers/monitoring_channel/files/vm-resolv.conf
+ dest=/etc/resolv.conf
+{% endif %}
+
+ - name: ceilometer proxy config
+ template: src=/opt/xos/observers/monitoring_channel/templates/ceilometer_proxy_config.j2 dest=/usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config mode=0777
+
+ - name: Monitoring channel upstart
+ template: src=/opt/xos/observers/monitoring_channel/templates/monitoring-channel.conf.j2 dest=/etc/init/monitoring-channel-{{ unique_id }}.conf
+
+ - name: Monitoring channel startup script
+ template: src=/opt/xos/observers/monitoring_channel/templates/start-monitoring-channel.sh.j2 dest=/usr/local/sbin/start-monitoring-channel-{{ unique_id }}.sh mode=0755
+ notify:
+# - restart monitoring-channel
+ - stop monitoring-channel
+ - remove container
+ - start monitoring-channel
+
+# These are samples, not necessary for correct function of demo
+
+ - name: Make sure Monitoring channel service is running
+ service: name=monitoring-channel-{{ unique_id }} state=started
+
+ handlers:
+ - name: restart monitoring-channel
+ shell: service monitoring-channel-{{ unique_id }} stop; sleep 1; service vcpe-{{ unique_id }} start
+
+ - name: stop monitoring-channel
+ service: name=monitoring-channel-{{ unique_id }} state=stopped
+
+ - name: remove container
+ docker: name=monitoring-channel-{{ unique_id }} state=absent image=docker-vcpe
+
+ - name: start monitoring-channel
+ service: name=monitoring-channel-{{ unique_id }} state=started
diff --git a/xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf b/xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf
new file mode 100644
index 0000000..1b78703
--- /dev/null
+++ b/xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf
@@ -0,0 +1,2 @@
+[program:monitoring_channel_observer]
+command=python /opt/xos/observers/monitoring_channel/monitoring_channel_observer.py -C /opt/xos/observers/monitoring_channel/monitoring_channel_observer_config
diff --git a/xos/observers/monitoring_channel/templates/Dockerfile.monitoring_channel b/xos/observers/monitoring_channel/templates/Dockerfile.monitoring_channel
new file mode 100644
index 0000000..45defb8
--- /dev/null
+++ b/xos/observers/monitoring_channel/templates/Dockerfile.monitoring_channel
@@ -0,0 +1,26 @@
+FROM ubuntu:14.04.2
+MAINTAINER Andy Bavier <acb@cs.princeton.edu>
+
+# XXX Workaround for docker bug:
+# https://github.com/docker/docker/issues/6345
+# Kernel 3.15 breaks docker, uss the line below as a workaround
+# until there is a fix
+RUN ln -s -f /bin/true /usr/bin/chfn
+# XXX End workaround
+
+# Install.
+RUN apt-get update && apt-get install -y \
+ python-pip \
+ python-dev
+
+RUN pip install web.py
+RUN pip install wsgilog
+RUN pip install python-ceilometerclient
+RUN mkdir -p /usr/local/share
+ADD ceilometer_proxy_server.py /usr/local/share/
+RUN chmod +x /usr/local/share/ceilometer_proxy_server.py
+ADD start_ceilometer_proxy /usr/local/sbin/
+RUN chmod +x /usr/local/sbin/start_ceilometer_proxy
+EXPOSE 8000
+WORKDIR /usr/local/share
+CMD /usr/local/sbin/start_ceilometer_proxy
diff --git a/xos/observers/monitoring_channel/templates/ceilometer_proxy_config.j2 b/xos/observers/monitoring_channel/templates/ceilometer_proxy_config.j2
new file mode 100644
index 0000000..cba6f2a
--- /dev/null
+++ b/xos/observers/monitoring_channel/templates/ceilometer_proxy_config.j2
@@ -0,0 +1,14 @@
+# This file autogenerated by monitoring-channel observer
+# It contains a list of attributes to be used by ceilometer proxy web server
+# syntax: key=value
+
+[default]
+auth_url={{ auth_url }}
+admin_user={{ admin_user }}
+admin_tenant={{ admin_tenant }}
+admin_password={{ admin_password }}
+
+[allowed_tenants]
+{% for tenant_id in allowed_tenant_ids %}
+{{ tenant_id }}
+{% endfor %}
diff --git a/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py b/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py
new file mode 100644
index 0000000..711e996
--- /dev/null
+++ b/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+import web
+import ConfigParser
+import io
+import json
+from ceilometerclient import client
+import logging
+from wsgilog import WsgiLog
+
+web.config.debug=False
+
+logfile = "ceilometer_proxy_server.log"
+level=logging.INFO
+logger=logging.getLogger('ceilometer_proxy_server')
+logger.setLevel(level)
+handler=logging.handlers.RotatingFileHandler(logfile,maxBytes=1000000, backupCount=1)
+logger.addHandler(handler)
+
+class FileLog(WsgiLog):
+ def __init__(self, application):
+ WsgiLog.__init__(
+ self,
+ application,
+ logformat = '%(message)s',
+ tofile = True,
+ toprint = True,
+ prnlevel = level,
+ file = logfile,
+ backups =1
+ )
+ def __call__(self, environ, start_response):
+ def hstart_response(status, response_headers, *args):
+ out = start_response(status, response_headers, *args)
+ try:
+ logline=environ["SERVER_PROTOCOL"]+" "+environ["REQUEST_METHOD"]+" "+environ["REQUEST_URI"]+" - "+status
+ except err:
+ logline="Could not log <%s> due to err <%s>" % (str(environ), err)
+ logger.info(logline)
+
+ return out
+
+ return super(FileLog, self).__call__(environ, hstart_response)
+
+#TODOs:
+#-See if we can avoid using python-ceilometerclient and instead use the REST calls directly with AuthToken
+#
+urls = (
+ r'^/v2/meters$', 'meter_list',
+ r'^/v2/meters/(?P<meter_name>[A-Za-z0-9_:.\-]+)/statistics$', 'statistics_list',
+ r'^/v2/samples$', 'sample_list',
+ r'^/v2/resources$', 'resource_list',
+)
+
+app = web.application(urls, globals())
+
+config = None
+ceilometer_client = None
+
+
+def parse_ceilometer_proxy_config():
+ global config
+ config = ConfigParser.RawConfigParser(allow_no_value=True)
+ config.read('ceilometer_proxy_config')
+
+def ceilometerclient():
+ global config, ceilometer_client
+ if ceilometer_client:
+ return ceilometer_client
+
+ if not config:
+ parse_ceilometer_proxy_config()
+
+ keystone = {}
+ keystone['os_username']=config.get('default','admin_user')
+ keystone['os_password']=config.get('default','admin_password')
+ keystone['os_auth_url']=config.get('default','auth_url')
+ keystone['os_tenant_name']=config.get('default','admin_tenant')
+ ceilometer_client = client.get_client(2,**keystone)
+ logger.info('ceilometer get_client is successful')
+ return ceilometer_client
+
+def make_query(user_id=None, tenant_id=None, resource_id=None,
+ user_ids=None, tenant_ids=None, resource_ids=None):
+ """Returns query built from given parameters.
+
+ This query can be then used for querying resources, meters and
+ statistics.
+
+ :Parameters:
+ - `user_id`: user_id, has a priority over list of ids
+ - `tenant_id`: tenant_id, has a priority over list of ids
+ - `resource_id`: resource_id, has a priority over list of ids
+ - `user_ids`: list of user_ids
+ - `tenant_ids`: list of tenant_ids
+ - `resource_ids`: list of resource_ids
+ """
+ user_ids = user_ids or []
+ tenant_ids = tenant_ids or []
+ resource_ids = resource_ids or []
+
+ query = []
+ if user_id:
+ user_ids = [user_id]
+ for u_id in user_ids:
+ query.append({"field": "user_id", "op": "eq", "value": u_id})
+
+ if tenant_id:
+ tenant_ids = [tenant_id]
+ for t_id in tenant_ids:
+ query.append({"field": "project_id", "op": "eq", "value": t_id})
+
+ if resource_id:
+ resource_ids = [resource_id]
+ for r_id in resource_ids:
+ query.append({"field": "resource_id", "op": "eq", "value": r_id})
+
+ return query
+
+def filter_query_params(query_params):
+ new_query=[]
+ i=0
+ user_specified_tenants=[]
+ for field in query_params['q.field']:
+ if field != 'project_id':
+ query = {}
+ query['field']=field
+ if query_params['q.op'][i] != '':
+ query['op']=query_params['q.op'][i]
+ query['value']=query_params['q.value'][i]
+ new_query.append(query)
+ else:
+ user_specified_tenants.append(query_params['q.value'][i])
+ i=i+1
+ return new_query,user_specified_tenants
+
+class meter_list:
+ def GET(self):
+ global config
+ keyword_args = {
+ "q.field": [],
+ "q.op": [],
+ "q.type": [],
+ "q.value": [],
+ }
+ query_params = web.input(**keyword_args)
+ new_query, user_specified_tenants = filter_query_params(query_params)
+
+ client = ceilometerclient()
+ meters=[]
+ for (k,v) in config.items('allowed_tenants'):
+ if user_specified_tenants and (k not in user_specified_tenants):
+ continue
+ final_query=[]
+ final_query.extend(new_query)
+ query = make_query(tenant_id=k)
+ final_query.extend(query)
+ logger.debug('final query=%s',final_query)
+ results = client.meters.list(q=final_query)
+ meters.extend(results)
+ return json.dumps([ob._info for ob in meters])
+
+class statistics_list:
+ def GET(self, meter_name):
+ global config
+ keyword_args = {
+ "q.field": [],
+ "q.op": [],
+ "q.type": [],
+ "q.value": [],
+ "period": None
+ }
+ query_params = web.input(**keyword_args)
+ new_query, user_specified_tenants = filter_query_params(query_params)
+
+ client = ceilometerclient()
+ period = query_params.period
+ statistics = []
+ for (k,v) in config.items('allowed_tenants'):
+ if user_specified_tenants and (k not in user_specified_tenants):
+ continue
+ final_query=[]
+ final_query.extend(new_query)
+ query = make_query(tenant_id=k)
+ final_query.extend(query)
+ logger.debug('final query=%s',final_query)
+ results = client.statistics.list(meter_name=meter_name, q=final_query, period=period)
+ statistics.extend(results)
+ return json.dumps([ob._info for ob in statistics])
+
+class sample_list:
+ def GET(self):
+ global config
+ keyword_args = {
+ "q.field": [],
+ "q.op": [],
+ "q.type": [],
+ "q.value": [],
+ }
+ query_params = web.input(**keyword_args)
+ new_query, user_specified_tenants = filter_query_params(query_params)
+
+ client = ceilometerclient()
+ samples=[]
+ for (k,v) in config.items('allowed_tenants'):
+ if user_specified_tenants and (k not in user_specified_tenants):
+ continue
+ final_query=[]
+ final_query.extend(new_query)
+ query = make_query(tenant_id=k)
+ final_query.extend(query)
+ logger.debug('final query=%s',final_query)
+ results = client.samples.list(q=query)
+ samples.extend(results)
+ return json.dumps([ob._info for ob in samples])
+
+class resource_list:
+ def GET(self):
+ global config
+ keyword_args = {
+ "q.field": [],
+ "q.op": [],
+ "q.type": [],
+ "q.value": [],
+ }
+ query_params = web.input(**keyword_args)
+ new_query, user_specified_tenants = filter_query_params(query_params)
+
+ client = ceilometerclient()
+ resources=[]
+ for (k,v) in config.items('allowed_tenants'):
+ if user_specified_tenants and (k not in user_specified_tenants):
+ continue
+ final_query=[]
+ final_query.extend(new_query)
+ query = make_query(tenant_id=k)
+ final_query.extend(query)
+ logger.debug('final query=%s',final_query)
+ results = client.resources.list(q=query)
+ resources.extend(results)
+ return json.dumps([ob._info for ob in resources])
+
+if __name__ == "__main__":
+ app.run(FileLog)
diff --git a/xos/observers/monitoring_channel/templates/monitoring-channel.conf.j2 b/xos/observers/monitoring_channel/templates/monitoring-channel.conf.j2
new file mode 100644
index 0000000..eb937ac
--- /dev/null
+++ b/xos/observers/monitoring_channel/templates/monitoring-channel.conf.j2
@@ -0,0 +1,10 @@
+# Upstart script for Monitoring channel
+description "Upstart script for Monitoring channel container"
+author "andy@onlab.us"
+start on filesystem and started docker
+stop on runlevel [!2345]
+respawn
+
+script
+ /usr/local/sbin/start-monitoring-channel-{{ unique_id }}.sh
+end script
diff --git a/xos/observers/monitoring_channel/templates/start-monitoring-channel.sh.j2 b/xos/observers/monitoring_channel/templates/start-monitoring-channel.sh.j2
new file mode 100755
index 0000000..10d9ef5
--- /dev/null
+++ b/xos/observers/monitoring_channel/templates/start-monitoring-channel.sh.j2
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+function mac_to_iface {
+ MAC=$1
+ ifconfig|grep $MAC| awk '{print $1}'|grep -v '\.'
+}
+
+function generate_mac_from_ip {
+ IP=$1
+ printf "02:42:%02x:%02x:%02x:%02x\n" `echo $IP|awk -F '.' '{print $1, $2, $3, $4}'`
+}
+
+iptables -L > /dev/null
+ip6tables -L > /dev/null
+
+MONITORING_CHANNEL=monitoring-channel-{{ unique_id }}
+HEADNODEFLATLANIP={{ headnode_flat_lan_ip }}
+
+docker inspect $MONITORING_CHANNEL > /dev/null 2>&1
+if [ "$?" == 1 ]
+then
+ #sudo docker build -t monitoring-channel -f Dockerfile.monitoring_channel .
+ sudo docker pull srikanthvavila/monitoring-channel
+ docker run -d --name=$MONITORING_CHANNEL --add-host="ctl:$HEADNODEFLATLANIP" --privileged=true -p 8888:8000 srikanthvavila/monitoring-channel
+else
+ docker start $MONITORING_CHANNEL
+fi
+
+# Set up networking via pipework
+#SHARED_LAN_IFACE=$( mac_to_iface {{ shared_lan_mac }} )
+#docker exec $MONITORING_CHANNEL ifconfig eth0 >> /dev/null || pipework $SHARED_LAN_IFACE -i eth0 $MONITORING_CHANNEL 192.168.0.1/24
+
+# Make sure VM's eth0 (hpc_client) has no IP address
+#ifconfig $HPC_IFACE 0.0.0.0
+
+# Now copy ceilometer proxy configuration to container
+cat /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config | docker exec -i $MONITORING_CHANNEL bash -c 'cat > /usr/local/share/ceilometer_proxy_config'
+
+# Attach to container
+docker start -a $MONITORING_CHANNEL
diff --git a/xos/observers/monitoring_channel/templates/start_ceilometer_proxy b/xos/observers/monitoring_channel/templates/start_ceilometer_proxy
new file mode 100644
index 0000000..ddaa9c8
--- /dev/null
+++ b/xos/observers/monitoring_channel/templates/start_ceilometer_proxy
@@ -0,0 +1 @@
+/usr/local/share/ceilometer_proxy_server.py 8000
diff --git a/xos/observers/onos/model-deps b/xos/observers/onos/model-deps
new file mode 100644
index 0000000..2da80e0
--- /dev/null
+++ b/xos/observers/onos/model-deps
@@ -0,0 +1,5 @@
+{
+ "ONOSApp": [
+ "ONOSService"
+ ]
+}
diff --git a/xos/observers/hello_world/helloworld-observer.py b/xos/observers/onos/onos-observer.py
similarity index 100%
copy from xos/observers/hello_world/helloworld-observer.py
copy to xos/observers/onos/onos-observer.py
diff --git a/xos/observers/onos/onos_observer_config b/xos/observers/onos/onos_observer_config
new file mode 100644
index 0000000..3c6d63d
--- /dev/null
+++ b/xos/observers/onos/onos_observer_config
@@ -0,0 +1,41 @@
+
+[plc]
+name=plc
+deployment=VICCI
+
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+host=128.112.171.237
+port=8000
+ssl_key=None
+ssl_cert=None
+ca_ssl_cert=None
+ratelimit_enabled=0
+omf_enabled=0
+mail_support_address=support@localhost
+nova_enabled=True
+
+[observer]
+name=onos
+dependency_graph=/opt/xos/observers/onos/model-deps
+steps_dir=/opt/xos/observers/onos/steps
+sys_dir=/opt/xos/observers/onos/sys
+deleters_dir=/opt/xos/observers/onos/deleters
+log_file=console
+driver=None
+pretend=False
+backoff_disabled=True
+save_ansible_output=True
+# set proxy_ssh to false on cloudlab
+proxy_ssh=False
+full_setup=True
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/observers/onos/run.sh b/xos/observers/onos/run.sh
new file mode 100755
index 0000000..ea4c511
--- /dev/null
+++ b/xos/observers/onos/run.sh
@@ -0,0 +1,6 @@
+#if [[ ! -e ./vcpe-observer.py ]]; then
+# ln -s ../../xos-observer.py vcpe-observer.py
+#fi
+
+export XOS_DIR=/opt/xos
+python onos-observer.py -C $XOS_DIR/observers/onos/onos_observer_config
diff --git a/xos/observers/onos/scripts/dockerip.sh b/xos/observers/onos/scripts/dockerip.sh
new file mode 100644
index 0000000..7684f3e
--- /dev/null
+++ b/xos/observers/onos/scripts/dockerip.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+docker inspect --format '{{ .NetworkSettings.IPAddress }}' $1
diff --git a/xos/observers/onos/start.sh b/xos/observers/onos/start.sh
new file mode 100755
index 0000000..c13ffbe
--- /dev/null
+++ b/xos/observers/onos/start.sh
@@ -0,0 +1,6 @@
+#if [[ ! -e ./vcpe-observer.py ]]; then
+# ln -s ../../xos-observer.py vcpe-observer.py
+#fi
+
+export XOS_DIR=/opt/xos
+nohup python onos-observer.py -C $XOS_DIR/observers/onos/onos_observer_config > /dev/null 2>&1 &
diff --git a/xos/observers/onos/steps/sync_onosapp.py b/xos/observers/onos/steps/sync_onosapp.py
new file mode 100644
index 0000000..8c97391
--- /dev/null
+++ b/xos/observers/onos/steps/sync_onosapp.py
@@ -0,0 +1,110 @@
+import hashlib
+import os
+import socket
+import sys
+import base64
+import time
+from django.db.models import F, Q
+from xos.config import Config
+from observer.syncstep import SyncStep
+from observer.ansible import run_template_ssh
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+from core.models import Service, Slice
+from services.onos.models import ONOSService, ONOSApp
+from util.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+logger = Logger(level=logging.INFO)
+
+class SyncONOSApp(SyncInstanceUsingAnsible):
+ provides=[ONOSApp]
+ observes=ONOSApp
+ requested_interval=0
+ template_name = "sync_onosapp.yaml"
+ service_key_name = "/opt/xos/observers/onos/onos_key"
+
+ def __init__(self, *args, **kwargs):
+ super(SyncONOSApp, self).__init__(*args, **kwargs)
+
+ def fetch_pending(self, deleted):
+ if (not deleted):
+ objs = ONOSApp.get_tenant_objects().filter(Q(enacted__lt=F('updated')) | Q(enacted=None),Q(lazy_blocked=False))
+ else:
+ objs = ONOSApp.get_deleted_tenant_objects()
+
+ return objs
+
+ def get_instance(self, o):
+ # We assume the ONOS service owns a slice, so pick one of the instances
+ # inside that slice to sync to.
+
+ serv = self.get_onos_service(o)
+
+ if serv.use_external_host:
+ return serv.use_external_host
+
+ if serv.slices.exists():
+ slice = serv.slices.all()[0]
+ if slice.instances.exists():
+ return slice.instances.all()[0]
+
+ return None
+
+ def get_onos_service(self, o):
+ if not o.provider_service:
+ return None
+
+ onoses = ONOSService.get_service_objects().filter(id=o.provider_service.id)
+ if not onoses:
+ return None
+
+ return onoses[0]
+
+ def get_files_dir(self, o):
+ if not hasattr(Config(), "observer_steps_dir"):
+ # make steps_dir mandatory; there's no valid reason for it to not
+ # be defined.
+ raise Exception("observer_steps_dir is not defined in config file")
+
+ step_dir = Config().observer_steps_dir
+
+ return os.path.join(step_dir, "..", "files", str(self.get_onos_service(o).id), o.name)
+
+ def write_configs(self, o):
+ o.config_fns = []
+ o.files_dir = self.get_files_dir(o)
+
+ if not os.path.exists(o.files_dir):
+ os.makedirs(o.files_dir)
+
+ for attr in o.tenantattributes.all():
+ if attr.name.startswith("config_"):
+ fn = attr.name[7:] # .replace("_json",".json")
+ o.config_fns.append(fn)
+ file(os.path.join(o.files_dir, fn),"w").write(attr.value)
+
+ def prepare_record(self, o):
+ self.write_configs(o)
+
+ def get_extra_attributes(self, o):
+ fields={}
+ fields["files_dir"] = o.files_dir
+ fields["appname"] = o.name
+ fields["nat_ip"] = self.get_instance(o).get_ssh_ip()
+ fields["config_fns"] = o.config_fns
+ fields["dependencies"] = [x.strip() for x in o.dependencies.split(",")]
+ fields["ONOS_container"] = "ONOS"
+ return fields
+
+ def sync_fields(self, o, fields):
+ # the super causes the playbook to be run
+ super(SyncONOSApp, self).sync_fields(o, fields)
+
+ def run_playbook(self, o, fields):
+ super(SyncONOSApp, self).run_playbook(o, fields)
+
+ def delete_record(self, m):
+ pass
diff --git a/xos/observers/onos/steps/sync_onosapp.yaml b/xos/observers/onos/steps/sync_onosapp.yaml
new file mode 100644
index 0000000..2c5eb0f
--- /dev/null
+++ b/xos/observers/onos/steps/sync_onosapp.yaml
@@ -0,0 +1,49 @@
+---
+- hosts: {{ instance_name }}
+ gather_facts: False
+ connection: ssh
+ user: ubuntu
+ sudo: yes
+ vars:
+ appname: {{ appname }}
+ dependencies: {{ dependencies }}
+
+ tasks:
+
+ - name: Config file directory
+ file:
+ path=/home/ubuntu/{{ appname }}/
+ state=directory
+
+ - name: Copy over configuration files
+ copy:
+ src={{ files_dir }}/{{ '{{' }} item {{ '}}' }}
+ dest=/home/ubuntu/{{ appname }}/{{ '{{' }} item {{ '}}' }}
+ with_items:
+ {% for config_fn in config_fns %}
+ - {{ config_fn }}
+ {% endfor %}
+
+ - name: Copy config files into container
+ shell: docker cp {{ appname }}/{{ '{{' }} item {{ '}}' }} {{ ONOS_container }}:/root/onos/config/
+ sudo: yes
+ with_items:
+ {% for config_fn in config_fns %}
+ - {{ config_fn }}
+ {% endfor %}
+
+ # Don't know how to check for this condition, just wait
+ - name: Wait for ONOS to install the apps
+ wait_for: timeout=15
+
+ - name: Add dependencies to ONOS
+ uri:
+ url: http://localhost:8181/onos/v1/applications/{{ '{{' }} item {{ '}}' }}/active
+ method: POST
+ user: karaf
+ password: karaf
+ with_items:
+ {% for dependency in dependencies %}
+ - {{ dependency }}
+ {% endfor %}
+
diff --git a/xos/observers/onos/steps/sync_onosservice.py b/xos/observers/onos/steps/sync_onosservice.py
new file mode 100644
index 0000000..65fa44e
--- /dev/null
+++ b/xos/observers/onos/steps/sync_onosservice.py
@@ -0,0 +1,73 @@
+import hashlib
+import os
+import socket
+import sys
+import base64
+import time
+from django.db.models import F, Q
+from xos.config import Config
+from observer.syncstep import SyncStep
+from observer.ansible import run_template_ssh
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+from core.models import Service, Slice
+from services.onos.models import ONOSService, ONOSApp
+from util.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+logger = Logger(level=logging.INFO)
+
+class SyncONOSService(SyncInstanceUsingAnsible):
+ provides=[ONOSService]
+ observes=ONOSService
+ requested_interval=0
+ template_name = "sync_onosservice.yaml"
+ service_key_name = "/opt/xos/observers/onos/onos_key"
+
+ def __init__(self, *args, **kwargs):
+ super(SyncONOSService, self).__init__(*args, **kwargs)
+
+ def fetch_pending(self, deleted):
+ if (not deleted):
+ objs = ONOSService.get_service_objects().filter(Q(enacted__lt=F('updated')) | Q(enacted=None),Q(lazy_blocked=False))
+ else:
+ objs = ONOSService.get_deleted_service_objects()
+
+ return objs
+
+ def get_instance(self, o):
+ # We assume the ONOS service owns a slice, so pick one of the instances
+ # inside that slice to sync to.
+
+ serv = o
+
+ if serv.use_external_host:
+ return serv.use_external_host
+
+ if serv.slices.exists():
+ slice = serv.slices.all()[0]
+ if slice.instances.exists():
+ return slice.instances.all()[0]
+
+ return None
+
+ def get_extra_attributes(self, o):
+ fields={}
+ fields["instance_hostname"] = self.get_instance(o).instance_name.replace("_","-")
+ fields["appname"] = o.name
+ fields["nat_ip"] = self.get_instance(o).get_ssh_ip()
+ fields["ONOS_container"] = "ONOS"
+ return fields
+
+ def sync_fields(self, o, fields):
+ # the super causes the playbook to be run
+
+ super(SyncONOSService, self).sync_fields(o, fields)
+
+ def run_playbook(self, o, fields):
+ super(SyncONOSService, self).run_playbook(o, fields)
+
+ def delete_record(self, m):
+ pass
diff --git a/xos/observers/onos/steps/sync_onosservice.yaml b/xos/observers/onos/steps/sync_onosservice.yaml
new file mode 100644
index 0000000..fd9c3db
--- /dev/null
+++ b/xos/observers/onos/steps/sync_onosservice.yaml
@@ -0,0 +1,66 @@
+---
+- hosts: {{ instance_name }}
+ gather_facts: False
+ connection: ssh
+ user: ubuntu
+ sudo: yes
+
+ tasks:
+
+ - name: Fix /etc/hosts
+ lineinfile:
+ dest=/etc/hosts
+ regexp="127.0.0.1 localhost"
+ line="127.0.0.1 localhost {{ instance_hostname }}"
+
+ - name: Add repo key
+ apt_key:
+ keyserver=hkp://pgp.mit.edu:80
+ id=58118E89F3A912897C070ADBF76221572C52609D
+
+ - name: Install Docker repo
+ apt_repository:
+ repo="deb https://apt.dockerproject.org/repo ubuntu-trusty main"
+ state=present
+
+ - name: Install Docker
+ apt:
+ name={{ '{{' }} item {{ '}}' }}
+ state=latest
+ update_cache=yes
+ with_items:
+ - docker-engine
+ - python-pip
+ - python-httplib2
+
+ - name: Install docker-py
+ pip:
+ name=docker-py
+ state=latest
+
+ - name: Start ONOS container
+ docker:
+ docker_api_version: "1.18"
+ name: {{ ONOS_container }}
+ # was: reloaded
+ state: running
+ image: onosproject/onos
+ ports:
+ - "6653:6653"
+ - "8101:8101"
+ - "8181:8181"
+ - "9876:9876"
+
+ - name: Get Docker IP
+ script: /opt/xos/observers/onos/scripts/dockerip.sh {{ ONOS_container }}
+ register: dockerip
+
+ - name: Wait for ONOS to come up
+ wait_for:
+ host={{ '{{' }} dockerip.stdout {{ '}}' }}
+ port={{ '{{' }} item {{ '}}' }}
+ state=present
+ with_items:
+ - 8101
+ - 8181
+ - 9876
diff --git a/xos/observers/onos/stop.sh b/xos/observers/onos/stop.sh
new file mode 100755
index 0000000..17d6eb7
--- /dev/null
+++ b/xos/observers/onos/stop.sh
@@ -0,0 +1 @@
+pkill -9 -f onos-observer.py
diff --git a/xos/observers/onos/supervisor/onos-observer.conf b/xos/observers/onos/supervisor/onos-observer.conf
new file mode 100644
index 0000000..16afa8c
--- /dev/null
+++ b/xos/observers/onos/supervisor/onos-observer.conf
@@ -0,0 +1,2 @@
+[program:onos-observer]
+command=python /opt/xos/observers/onos/onos-observer.py -C /opt/xos/observers/onos/onos_observer_config
diff --git a/xos/observers/vbng/steps/sync_vbngtenant.py b/xos/observers/vbng/steps/sync_vbngtenant.py
index 8868d6f..b603ed6 100644
--- a/xos/observers/vbng/steps/sync_vbngtenant.py
+++ b/xos/observers/vbng/steps/sync_vbngtenant.py
@@ -50,9 +50,38 @@
def get_vbng_url(self, o):
service = self.get_vbng_service(o)
- if not service.vbng_url:
- raise Exception("vBNG service does not have vbng_url set")
- return service.vbng_url
+
+ # if the service object specifies a vbng_url, then use it
+ if service.vbng_url:
+ return service.vbng_url
+
+ # otherwise, see if the service has tenancy in ONOS
+ for tenant in service.subscribed_tenants.all():
+ if tenant.provider_service and tenant.provider_service.kind == "onos":
+ onos_service = tenant.provider_service
+ if not onos_service.slices.exists():
+ raise Exception("vBNG service is linked to an ONOSApp, but the App's Service has no slices")
+ onos_slice = onos_service.slices.all()[0]
+ if not onos_slice.instances.exists():
+ raise Exception("vBNG service is linked to an ONOSApp, but the App's Service's Slice has no instances")
+ instance = onos_slice.instances.all()[0]
+
+ #onos_app = ONOSApp.objects.filter(id = tenant.id)
+ #instance = onos_app.instance
+ #if not instance:
+ # raise Exception("ONOSApp has no instance")
+
+ if not instance.instance_name:
+ raise Exception("vBNG service is linked to an ONOSApp, but the App's Service's Slice's first instance is not instantiated")
+ ip = instance.get_network_ip("nat")
+ if not ip:
+ raise Exception("vBNG service is linked to an ONOSApp, but the App's Service's Slice's first instance does not have an ip")
+
+ logger.info("Using ip %s from ONOS Instance %s" % (ip, instance))
+
+ return "http://%s:8181/onos/virtualbng/" % ip
+
+ raise Exception("vBNG service does not have vbng_url set, and is not linked to an ONOSApp")
def get_private_interface(self, o):
vcpes = VCPETenant.get_tenant_objects().all()
diff --git a/xos/observers/vcpe/steps/sync_vcpetenant.yaml b/xos/observers/vcpe/steps/sync_vcpetenant.yaml
index 1a30656..fac78d5 100644
--- a/xos/observers/vcpe/steps/sync_vcpetenant.yaml
+++ b/xos/observers/vcpe/steps/sync_vcpetenant.yaml
@@ -35,6 +35,11 @@
private_mac: {{ private_mac }}
hpc_client_ip: {{ hpc_client_ip }}
hpc_client_mac: {{ hpc_client_mac }}
+ keystone_tenant_id: {{ keystone_tenant_id }}
+ keystone_user_id: {{ keystone_user_id }}
+ rabbit_user: {{ rabbit_user }}
+ rabbit_password: {{ rabbit_password }}
+ rabbit_host: {{ rabbit_host }}
tasks:
{% if full_setup %}
@@ -73,6 +78,25 @@
- name: Install resolv.conf
copy: src=/opt/xos/observers/vcpe/files/vm-resolv.conf
dest=/etc/resolv.conf
+
+ - name: make sure ~/bin exists
+ file: path=~/bin state=directory owner=root group=root
+
+ - name: Copy cron job to destination
+ copy: src=/opt/xos/observers/vcpe/vcpe_stats_notifier.py
+ dest=~/bin/vcpe_stats_notifier.py
+
+ - name: install python-kombu
+ apt: name=python-kombu state=present
+
+ - name: Clean any running vcpe_stats_notifier cron jobs
+ command: pkill vcpe_stats_notifier.py
+ ignore_errors: yes
+
+ - name: Initiate vcpe_stats_notifier cron job
+ command: python ~/bin/vcpe_stats_notifier.py --keystone_tenant_id={{ keystone_tenant_id }} --keystone_user_id={{ keystone_user_id }} --rabbit_user={{ rabbit_user }} --rabbit_password={{ rabbit_password }} --rabbit_host={{ rabbit_host }} --vcpeservice_rabbit_exchange='vcpeservice'
+ async: 999999999999999999
+ poll: 0
{% endif %}
- name: vCPE upstart
diff --git a/xos/observers/vcpe/vcpe_stats_notifier.py b/xos/observers/vcpe/vcpe_stats_notifier.py
new file mode 100644
index 0000000..f4bb923
--- /dev/null
+++ b/xos/observers/vcpe/vcpe_stats_notifier.py
@@ -0,0 +1,250 @@
+import six
+import uuid
+import datetime
+from kombu.connection import BrokerConnection
+from kombu.messaging import Exchange, Queue, Consumer, Producer
+import subprocess
+import re
+import time, threading
+import sys, getopt
+import logging
+
+
+logfile = "vcpe_stats_notifier.log"
+level=logging.INFO
+logger=logging.getLogger('vcpe_stats_notifier')
+logger.setLevel(level)
+handler=logging.handlers.RotatingFileHandler(logfile,maxBytes=1000000, backupCount=1)
+logger.addHandler(handler)
+
+def extract_dns_stats_from_all_vcpes():
+ p = subprocess.Popen('docker ps', shell=True, stdout=subprocess.PIPE)
+ firstline = True
+ dockercontainers = {}
+ while True:
+ out = p.stdout.readline()
+ if out == '' and p.poll() != None:
+ break
+ if out != '':
+ if firstline is True:
+ firstline = False
+ else:
+ fields = out.split()
+ container_fields = {}
+ container_fields['id'] = fields[0]
+ dockercontainers[fields[-1]] = container_fields
+ for k,v in dockercontainers.iteritems():
+ cmd = 'docker exec ' + v['id'] + ' killall -10 dnsmasq'
+ p = subprocess.Popen (cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ (output, error) = p.communicate()
+ if error:
+ logger.error("killall dnsmasq command failed with error = %s",error)
+ continue
+ cmd = 'docker exec ' + v['id'] + ' tail -7 /var/log/syslog'
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ (output, error) = p.communicate()
+ if error:
+ logger.error("tail on dnsmasq log command failed with error = %s",error)
+ continue
+ log_list = output.splitlines()
+ i = 0
+ while i < len(log_list):
+ m = re.search('(?<=:\scache size\s)(\S*)(?=,\s),\s(\S*)(?=/)/(\S*)(?=\scache insertions re-used unexpired cache entries)', log_list[i])
+ if m == None:
+ i = i+1
+ continue;
+ v['cache_size'] = m.group(1)
+ v['replaced_unexpired_entries'] = m.group(2)
+ v['total_inserted_entries'] = m.group(3)
+ i = i+1
+ m = re.search('(?<=:\squeries forwarded\s)(\S*)(?=,),\squeries answered locally\s(\S*)(?=$)', log_list[i])
+ v['queries_forwarded'] = m.group(1)
+ v['queries_answered_locally'] = m.group(2)
+ break;
+ i = i+2
+ v['server_stats'] = []
+ while i < len(log_list):
+ m = re.search('(?<=:\sserver\s)(\S*)(?=#)#\d*:\squeries sent\s(\S*)(?=,),\sretried or failed\s(\S*)(?=$)', log_list[i])
+ if m == None:
+ i = i+1
+ continue
+ dns_server = {}
+ dns_server['id'] = m.group(1)
+ dns_server['queries_sent'] = m.group(2)
+ dns_server['queries_failed'] = m.group(3)
+ v['server_stats'].append(dns_server)
+ i = i+1
+ return dockercontainers
+
+
+keystone_tenant_id='3a397e70f64e4e40b69b6266c634d9d0'
+keystone_user_id='1e3ce043029547f1a61c1996d1a531a2'
+rabbit_user='openstack'
+rabbit_password='80608318c273f348a7c3'
+rabbit_host='10.11.10.1'
+vcpeservice_rabbit_exchange='vcpeservice'
+cpe_publisher_id='vcpe_publisher'
+
+producer = None
+
+def setup_rabbit_mq_channel():
+ global producer
+ global rabbit_user, rabbit_password, rabbit_host, vcpeservice_rabbit_exchange,cpe_publisher_id
+ vcpeservice_exchange = Exchange(vcpeservice_rabbit_exchange, "topic", durable=False)
+ # connections/channels
+ connection = BrokerConnection(rabbit_host, rabbit_user, rabbit_password)
+ logger.info('Connection to RabbitMQ server successful')
+ channel = connection.channel()
+ # produce
+ producer = Producer(channel, exchange=vcpeservice_exchange, routing_key='notifications.info')
+ p = subprocess.Popen('hostname', shell=True, stdout=subprocess.PIPE)
+ (hostname, error) = p.communicate()
+ cpe_publisher_id = cpe_publisher_id + '_on_' + hostname
+ logger.info('cpe_publisher_id=%s',cpe_publisher_id)
+
+def publish_cpe_stats():
+ global producer
+ global keystone_tenant_id, keystone_user_id, cpe_publisher_id
+ cpe_container_stats = extract_dns_stats_from_all_vcpes()
+
+ for k,v in cpe_container_stats.iteritems():
+ msg = {'event_type': 'vcpe',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id
+ }
+ }
+ producer.publish(msg)
+ if 'cache_size' in v:
+ msg = {'event_type': 'vcpe.dns.cache.size',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'cache_size':v['cache_size']
+ }
+ }
+ producer.publish(msg)
+ if 'total_inserted_entries' in v:
+ msg = {'event_type': 'vcpe.dns.total_inserted_entries',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'total_inserted_entries':v['total_inserted_entries']
+ }
+ }
+ producer.publish(msg)
+ if 'replaced_unexpired_entries' in v:
+ msg = {'event_type': 'vcpe.dns.replaced_unexpired_entries',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'replaced_unexpired_entries':v['replaced_unexpired_entries']
+ }
+ }
+ producer.publish(msg)
+
+ if 'queries_forwarded' in v:
+ msg = {'event_type': 'vcpe.dns.queries_forwarded',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'queries_forwarded':v['queries_forwarded']
+ }
+ }
+ producer.publish(msg)
+
+ if 'queries_answered_locally' in v:
+ msg = {'event_type': 'vcpe.dns.queries_answered_locally',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'queries_answered_locally':v['queries_answered_locally']
+ }
+ }
+ producer.publish(msg)
+
+ if 'server_stats' in v:
+ for server in v['server_stats']:
+ msg = {'event_type': 'vcpe.dns.server.queries_sent',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'upstream_server':server['id'],
+ 'queries_sent':server['queries_sent']
+ }
+ }
+ producer.publish(msg)
+
+ msg = {'event_type': 'vcpe.dns.server.queries_failed',
+ 'message_id':six.text_type(uuid.uuid4()),
+ 'publisher_id': cpe_publisher_id,
+ 'timestamp':datetime.datetime.now().isoformat(),
+ 'priority':'INFO',
+ 'payload': {'vcpe_id':k,
+ 'user_id':keystone_user_id,
+ 'tenant_id':keystone_tenant_id,
+ 'upstream_server':server['id'],
+ 'queries_failed':server['queries_failed']
+ }
+ }
+ producer.publish(msg)
+
+def periodic_publish():
+ publish_cpe_stats()
+ #Publish every 5minutes
+ threading.Timer(300, periodic_publish).start()
+
+def main(argv):
+ global keystone_tenant_id, keystone_user_id, rabbit_user, rabbit_password, rabbit_host, vcpeservice_rabbit_exchange
+ try:
+ opts, args = getopt.getopt(argv,"",["keystone_tenant_id=","keystone_user_id=","rabbit_host=","rabbit_user=","rabbit_password=","vcpeservice_rabbit_exchange="])
+ except getopt.GetoptError:
+ print 'vcpe_stats_notifier.py keystone_tenant_id=<keystone_tenant_id> keystone_user_id=<keystone_user_id> rabbit_host=<IP addr> rabbit_user=<user> rabbit_password=<password> vcpeservice_rabbit_exchange=<exchange name>'
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt in ("--keystone_tenant_id"):
+ keystone_tenant_id = arg
+ elif opt in ("--keystone_user_id"):
+ keystone_user_id = arg
+ elif opt in ("--rabbit_user"):
+ rabbit_user = arg
+ elif opt in ("--rabbit_password"):
+ rabbit_password = arg
+ elif opt in ("--rabbit_host"):
+ rabbit_host = arg
+ elif opt in ("--vcpeservice_rabbit_exchange"):
+ vcpeservice_rabbit_exchange = arg
+ logger.info("vcpe_stats_notifier args:keystone_tenant_id=%s keystone_user_id=%s rabbit_user=%s rabbit_host=%s vcpeservice_rabbit_exchange=%s",keystone_tenant_id,keystone_user_id,rabbit_user,rabbit_host,vcpeservice_rabbit_exchange)
+ setup_rabbit_mq_channel()
+ periodic_publish()
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/xos/openstack_observer/steps/sync_controller_networks.py b/xos/openstack_observer/steps/sync_controller_networks.py
index 1e7805f..ef238ee 100644
--- a/xos/openstack_observer/steps/sync_controller_networks.py
+++ b/xos/openstack_observer/steps/sync_controller_networks.py
@@ -69,7 +69,7 @@
if (controller_network.network.template.name!='Private'):
logger.info("skipping network controller %s because it is not private" % controller_network)
# We only sync private networks
- return
+ return SyncStep.SYNC_WITHOUT_RUNNING
if not controller_network.controller.admin_user:
logger.info("controller %r has no admin_user, skipping" % controller_network.controller)
diff --git a/xos/openstack_observer/steps/sync_instances.py b/xos/openstack_observer/steps/sync_instances.py
index 7aa4bb7..1209448 100644
--- a/xos/openstack_observer/steps/sync_instances.py
+++ b/xos/openstack_observer/steps/sync_instances.py
@@ -56,10 +56,12 @@
controller=instance.node.site_deployment.controller)
for controller_network in controller_networks:
+
+ # Lenient exception - causes slow backoff
if controller_network.network.template.visibility == 'private' and \
controller_network.network.template.translation == 'none':
if not controller_network.net_id:
- raise Exception("Private Network %s has no id; Try again later" % controller_network.network.name)
+ raise DeferredException("Private Network %s has no id; Try again later" % controller_network.network.name)
nics.append(controller_network.net_id)
# now include network template
@@ -108,7 +110,7 @@
userData = self.get_userdata(instance, pubkeys)
if instance.userData:
- userData = instance.userData
+ userData += instance.userData
controller = instance.node.site_deployment.controller
fields = {'endpoint':controller.auth_url,
diff --git a/xos/openstack_observer/syncstep.py b/xos/openstack_observer/syncstep.py
index 66225d0..7accbfa 100644
--- a/xos/openstack_observer/syncstep.py
+++ b/xos/openstack_observer/syncstep.py
@@ -45,6 +45,12 @@
psmodel Model name the step synchronizes
dependencies list of names of models that must be synchronized first if the current model depends on them
"""
+
+ # map_sync_outputs can return this value to cause a step to be marked
+ # successful without running ansible. Used for sync_network_controllers
+ # on nat networks.
+ SYNC_WITHOUT_RUNNING = "sync_without_running"
+
slow=False
def get_prop(self, prop):
try:
@@ -128,6 +134,8 @@
pass
tenant_fields = self.map_sync_inputs(o)
+ if tenant_fields == SyncStep.SYNC_WITHOUT_RUNNING:
+ return
main_objs=self.observes
if (type(main_objs) is list):
main_objs=main_objs[0]
diff --git a/xos/scripts/opencloud b/xos/scripts/opencloud
index 6705b20..b936ce3 100755
--- a/xos/scripts/opencloud
+++ b/xos/scripts/opencloud
@@ -147,6 +147,7 @@
python ./manage.py makemigrations syndicate_storage
python ./manage.py makemigrations cord
python ./manage.py makemigrations ceilometer
+ python ./manage.py makemigrations onos
#python ./manage.py makemigrations servcomp
}
diff --git a/xos/services/__init__.py b/xos/services/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/services/__init__.py
@@ -0,0 +1 @@
+
diff --git a/xos/services/onos/__init__.py b/xos/services/onos/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/services/onos/__init__.py
@@ -0,0 +1 @@
+
diff --git a/xos/services/onos/admin.py b/xos/services/onos/admin.py
new file mode 100644
index 0000000..d13a991
--- /dev/null
+++ b/xos/services/onos/admin.py
@@ -0,0 +1,112 @@
+from django.contrib import admin
+
+from services.onos.models import *
+from django import forms
+from django.utils.safestring import mark_safe
+from django.contrib.auth.admin import UserAdmin
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.signals import user_logged_in
+from django.utils import timezone
+from django.contrib.contenttypes import generic
+from suit.widgets import LinkedSelect
+from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, ServicePrivilegeInline, TenantRootTenantInline, TenantRootPrivilegeInline, TenantAttrAsTabInline
+from core.middleware import get_request
+
+from functools import update_wrapper
+from django.contrib.admin.views.main import ChangeList
+from django.core.urlresolvers import reverse
+from django.contrib.admin.utils import quote
+
+class ONOSServiceForm(forms.ModelForm):
+ use_external_host = forms.CharField(required=False)
+
+ def __init__(self,*args,**kwargs):
+ super (ONOSServiceForm,self ).__init__(*args,**kwargs)
+ if self.instance:
+ # fields for the attributes
+ self.fields['use_external_host'].initial = self.instance.use_external_host
+
+ def save(self, commit=True):
+ self.instance.use_external_host = self.cleaned_data.get("use_external_host")
+ return super(ONOSServiceForm, self).save(commit=commit)
+
+ class Meta:
+ model = ONOSService
+
+class ONOSServiceAdmin(ReadOnlyAwareAdmin):
+ model = ONOSService
+ verbose_name = "ONOS Service"
+ verbose_name_plural = "ONOS Services"
+ 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", "use_external_host" ], 'classes':['suit-tab suit-tab-general']})]
+ readonly_fields = ('backend_status_text', )
+ inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
+ form = ONOSServiceForm
+
+ extracontext_registered_admins = True
+
+ user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+ suit_form_tabs =(('general', 'ONOS Service Details'),
+ ('administration', 'Administration'),
+ ('slices','Slices'),
+ ('serviceattrs','Additional Attributes'),
+ ('serviceprivileges','Privileges'),
+ )
+
+ suit_form_includes = (('onosadmin.html', 'top', 'administration'),
+ )
+
+ def queryset(self, request):
+ return ONOSService.get_service_objects_by_user(request.user)
+
+class ONOSAppForm(forms.ModelForm):
+ creator = forms.ModelChoiceField(queryset=User.objects.all())
+ name = forms.CharField()
+ dependencies = forms.CharField(required=False)
+
+ def __init__(self,*args,**kwargs):
+ super (ONOSAppForm,self ).__init__(*args,**kwargs)
+ self.fields['kind'].widget.attrs['readonly'] = True
+ self.fields['provider_service'].queryset = ONOSService.get_service_objects().all()
+ if self.instance:
+ # fields for the attributes
+ self.fields['creator'].initial = self.instance.creator
+ self.fields['name'].initial = self.instance.name
+ self.fields['dependencies'].initial = self.instance.dependencies
+ if (not self.instance) or (not self.instance.pk):
+ # default fields for an 'add' form
+ self.fields['kind'].initial = ONOS_KIND
+ self.fields['creator'].initial = get_request().user
+ if ONOSService.get_service_objects().exists():
+ self.fields["provider_service"].initial = ONOSService.get_service_objects().all()[0]
+
+ def save(self, commit=True):
+ self.instance.creator = self.cleaned_data.get("creator")
+ self.instance.name = self.cleaned_data.get("name")
+ self.instance.dependencies = self.cleaned_data.get("dependencies")
+ return super(ONOSAppForm, self).save(commit=commit)
+
+ class Meta:
+ model = ONOSApp
+
+class ONOSAppAdmin(ReadOnlyAwareAdmin):
+ list_display = ('backend_status_icon', 'name', )
+ list_display_links = ('backend_status_icon', 'name')
+ fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'name', 'provider_service', 'subscriber_service', 'service_specific_attribute', "dependencies",
+ 'creator'],
+ 'classes':['suit-tab suit-tab-general']})]
+ readonly_fields = ('backend_status_text', 'instance', 'service_specific_attribute')
+ inlines = [TenantAttrAsTabInline]
+ form = ONOSAppForm
+
+ suit_form_tabs = (('general','Details'), ('tenantattrs', 'Attributes'))
+
+ def queryset(self, request):
+ return ONOSApp.get_tenant_objects_by_user(request.user)
+
+admin.site.register(ONOSService, ONOSServiceAdmin)
+admin.site.register(ONOSApp, ONOSAppAdmin)
+
diff --git a/xos/services/onos/models.py b/xos/services/onos/models.py
new file mode 100644
index 0000000..80e903e
--- /dev/null
+++ b/xos/services/onos/models.py
@@ -0,0 +1,122 @@
+from django.db import models
+from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber
+from core.models.plcorebase import StrippedCharField
+import os
+from django.db import models, transaction
+from django.forms.models import model_to_dict
+from django.db.models import Q
+from operator import itemgetter, attrgetter, methodcaller
+import traceback
+from xos.exceptions import *
+from core.models import SlicePrivilege, SitePrivilege
+from sets import Set
+
+ONOS_KIND = "onos"
+
+class ONOSService(Service):
+ KIND = ONOS_KIND
+
+ class Meta:
+ app_label = "onos"
+ verbose_name = "ONOS Service"
+ proxy = True
+
+ default_attributes = {"use_external_host": ""}
+
+ @property
+ def use_external_host(self):
+ return self.get_attribute("use_external_host", self.default_attributes["use_external_host"])
+
+ @use_external_host.setter
+ def use_external_host(self, value):
+ self.set_attribute("use_external_host", value)
+
+class ONOSApp(Tenant): # aka 'ONOSTenant'
+ class Meta:
+ proxy = True
+
+ KIND = ONOS_KIND
+
+ default_attributes = {"name": "",
+ "dependencies": ""}
+ def __init__(self, *args, **kwargs):
+ onos_services = ONOSService.get_service_objects().all()
+ if onos_services:
+ self._meta.get_field("provider_service").default = onos_services[0].id
+ super(ONOSApp, self).__init__(*args, **kwargs)
+
+ @property
+ def creator(self):
+ from core.models import User
+ if getattr(self, "cached_creator", None):
+ return self.cached_creator
+ creator_id=self.get_attribute("creator_id")
+ if not creator_id:
+ return None
+ users=User.objects.filter(id=creator_id)
+ if not users:
+ return None
+ user=users[0]
+ self.cached_creator = users[0]
+ return user
+
+ @creator.setter
+ def creator(self, value):
+ if value:
+ value = value.id
+ if (value != self.get_attribute("creator_id", None)):
+ self.cached_creator=None
+ self.set_attribute("creator_id", value)
+
+ @property
+ def name(self):
+ return self.get_attribute("name", self.default_attributes["name"])
+
+ @name.setter
+ def name(self, value):
+ self.set_attribute("name", value)
+
+ @property
+ def dependencies(self):
+ return self.get_attribute("dependencies", self.default_attributes["dependencies"])
+
+ @dependencies.setter
+ def dependencies(self, value):
+ self.set_attribute("dependencies", value)
+
+ #@property
+ #def instance(self):
+ # instance_id = self.get_attribute("instance_id", self.default_attributes["instance_id"])
+ # if instance_id:
+ # instances = Instance.objects.filter(id=instance_id)
+ # if instances:
+ # return instances[0]
+ # return None
+
+ #@instance.setter
+ #def instance(self, value):
+ # self.set_attribute("instance_id", value.id)
+
+ def save(self, *args, **kwargs):
+ if not self.creator:
+ if not getattr(self, "caller", None):
+ # caller must be set when creating a vCPE since it creates a slice
+ raise XOSProgrammingError("ONOSApp's self.caller was not set")
+ self.creator = self.caller
+ if not self.creator:
+ raise XOSProgrammingError("ONOSApp's self.creator was not set")
+
+ super(ONOSApp, self).save(*args, **kwargs)
+ model_policy_onos_app(self.pk)
+
+# TODO: Probably don't need this...
+def model_policy_onos_app(pk):
+ # TODO: this should be made in to a real model_policy
+ with transaction.atomic():
+ oa = ONOSApp.objects.select_for_update().filter(pk=pk)
+ if not oa:
+ return
+ oa = oa[0]
+ #oa.manage_container()
+
+
diff --git a/xos/services/onos/templates/onosadmin.html b/xos/services/onos/templates/onosadmin.html
new file mode 100644
index 0000000..1e8d42c
--- /dev/null
+++ b/xos/services/onos/templates/onosadmin.html
@@ -0,0 +1,6 @@
+<div class = "left-nav">
+<ul>
+<li><a href="/admin/onos/onosapp/">ONOS Apps</a></li>
+</ul>
+</div>
+
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 81d1333..7a94d8b 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -12,7 +12,11 @@
no-create:
type: boolean
default: false
- description: Do not allow Tosca to create this object)
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object)
# Service
define(xos_base_service_caps,
scalable:
@@ -59,7 +63,7 @@
service_specific_id:
type: string
required: false
- description: Service specific ID, opaque to XOS but meaningful to service)
+ description: Service specific ID opaque to XOS but meaningful to service)
define(xos_base_tenant_props,
kind:
type: string
@@ -68,7 +72,7 @@
service_specific_id:
type: string
required: false
- description: Service specific ID, opaque to XOS but meaningful to service)
+ description: Service specific ID opaque to XOS but meaningful to service)
# end m4 macros
#
@@ -85,6 +89,41 @@
properties:
xos_base_service_props
+ tosca.nodes.ONOSService:
+ derived_from: tosca.nodes.Root
+ description: >
+ ONOS Service
+ capabilities:
+ xos_base_service_caps
+ properties:
+ xos_base_service_props
+
+ tosca.nodes.ONOSApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS Application.
+ properties:
+ xos_base_tenant_props
+ dependencies:
+ type: string
+ required: false
+
+ tosca.nodes.ONOSvBNGApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS Application.
+ properties:
+ xos_base_tenant_props
+ dependencies:
+ type: string
+ required: false
+ config_addresses.json:
+ type: string
+ required: false
+ config_virtualbng.json:
+ type: string
+ required: false
+
tosca.nodes.VCPEService:
description: >
CORD: The vCPE Service.
@@ -410,6 +449,7 @@
controller:
type: tosca.capabilities.xos.Controller
properties:
+ xos_base_props
backend_type:
type: string
required: false
@@ -447,29 +487,30 @@
site:
type: tosca.capabilities.xos.Site
properties:
- display_name:
- type: string
- required: false
- description: Name of the site.
- site_url:
- type: string
- required: false
- description: URL of site web page.
- enabled:
- type: boolean
- default: true
- hosts_nodes:
- type: boolean
- default: true
- description: If True, then this site hosts nodes where Instances may be instantiated.
- hosts_users:
- type: boolean
- default: true
- description: If True, then this site hosts users who may use XOS.
- is_public:
- type: boolean
- default: true
- # location, longitude, latitude
+ xos_base_props
+ display_name:
+ type: string
+ required: false
+ description: Name of the site.
+ site_url:
+ type: string
+ required: false
+ description: URL of site web page.
+ enabled:
+ type: boolean
+ default: true
+ hosts_nodes:
+ type: boolean
+ default: true
+ description: If True, then this site hosts nodes where Instances may be instantiated.
+ hosts_users:
+ type: boolean
+ default: true
+ description: If True, then this site hosts users who may use XOS.
+ is_public:
+ type: boolean
+ default: true
+ # location, longitude, latitude
tosca.nodes.Slice:
derived_from: tosca.nodes.Root
@@ -480,6 +521,7 @@
slice:
type: tosca.capabilities.xos.Slice
properties:
+ xos_base_props
enabled:
type: boolean
default: true
@@ -501,7 +543,9 @@
description: >
An XOS Node. Nodes are physical machines that host virtual machines
and/or containers.
- capability:
+ properties:
+ xos_base_props
+ capabilities:
node:
type: tosca.capabilities.xos.Node
@@ -525,6 +569,10 @@
derived_from: tosca.relationships.Root
valid_target_types: [ tosca.capabilities.xos.Service ]
+ tosca.relationships.UsedByService:
+ derived_from: tosca.relationships.Root
+ valid_target_types: [ tosca.capabilities.xos.Service ]
+
tosca.relationships.ControllerDeployment:
derived_from: tosca.relationships.Root
valid_target_types: [ tosca.capabilities.xos.Deployment ]
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 9e25754..9b307d6 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -57,6 +57,84 @@
required: false
description: Version number of Service.
+ tosca.nodes.ONOSService:
+ derived_from: tosca.nodes.Root
+ description: >
+ ONOS Service
+ capabilities:
+ scalable:
+ type: tosca.capabilities.Scalable
+ service:
+ type: tosca.capabilities.xos.Service
+ properties:
+ kind:
+ type: string
+ default: generic
+ description: Type of service.
+ view_url:
+ type: string
+ required: false
+ description: URL to follow when icon is clicked in the Service Directory.
+ icon_url:
+ type: string
+ required: false
+ description: ICON to display in the Service Directory.
+ enabled:
+ type: boolean
+ default: true
+ published:
+ type: boolean
+ default: true
+ description: If True then display this Service in the Service Directory.
+ public_key:
+ type: string
+ required: false
+ description: Public key to install into Instances to allows Services to SSH into them.
+ versionNumber:
+ type: string
+ required: false
+ description: Version number of Service.
+
+ tosca.nodes.ONOSApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS Application.
+ properties:
+ kind:
+ type: string
+ default: generic
+ description: Kind of tenant
+ service_specific_id:
+ type: string
+ required: false
+ description: Service specific ID opaque to XOS but meaningful to service
+ dependencies:
+ type: string
+ required: false
+
+ tosca.nodes.ONOSvBNGApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS Application.
+ properties:
+ kind:
+ type: string
+ default: generic
+ description: Kind of tenant
+ service_specific_id:
+ type: string
+ required: false
+ description: Service specific ID opaque to XOS but meaningful to service
+ dependencies:
+ type: string
+ required: false
+ config_addresses.json:
+ type: string
+ required: false
+ config_virtualbng.json:
+ type: string
+ required: false
+
tosca.nodes.VCPEService:
description: >
CORD: The vCPE Service.
@@ -193,7 +271,7 @@
service_specific_id:
type: string
required: false
- description: Service specific ID
+ description: Service specific ID opaque to XOS but meaningful to service
tosca.nodes.CORDSubscriber:
derived_from: tosca.nodes.Root
@@ -212,7 +290,7 @@
service_specific_id:
type: string
required: false
- description: Service specific ID
+ description: Service specific ID opaque to XOS but meaningful to service
firewall_enable:
type: boolean
default: false
@@ -262,7 +340,7 @@
service_specific_id:
type: string
required: false
- description: Service specific ID
+ description: Service specific ID opaque to XOS but meaningful to service
vlan_id:
type: string
required: false
@@ -460,6 +538,10 @@
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
accessControl:
type: string
default: allow all
@@ -499,6 +581,18 @@
controller:
type: tosca.capabilities.xos.Controller
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
backend_type:
type: string
required: false
@@ -536,29 +630,41 @@
site:
type: tosca.capabilities.xos.Site
properties:
- display_name:
- type: string
- required: false
- description: Name of the site.
- site_url:
- type: string
- required: false
- description: URL of site web page.
- enabled:
- type: boolean
- default: true
- hosts_nodes:
- type: boolean
- default: true
- description: If True, then this site hosts nodes where Instances may be instantiated.
- hosts_users:
- type: boolean
- default: true
- description: If True, then this site hosts users who may use XOS.
- is_public:
- type: boolean
- default: true
- # location, longitude, latitude
+ 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
+ display_name:
+ type: string
+ required: false
+ description: Name of the site.
+ site_url:
+ type: string
+ required: false
+ description: URL of site web page.
+ enabled:
+ type: boolean
+ default: true
+ hosts_nodes:
+ type: boolean
+ default: true
+ description: If True, then this site hosts nodes where Instances may be instantiated.
+ hosts_users:
+ type: boolean
+ default: true
+ description: If True, then this site hosts users who may use XOS.
+ is_public:
+ type: boolean
+ default: true
+ # location, longitude, latitude
tosca.nodes.Slice:
derived_from: tosca.nodes.Root
@@ -569,6 +675,18 @@
slice:
type: tosca.capabilities.xos.Slice
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
enabled:
type: boolean
default: true
@@ -590,7 +708,20 @@
description: >
An XOS Node. Nodes are physical machines that host virtual machines
and/or containers.
- capability:
+ 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.Node
@@ -614,6 +745,10 @@
derived_from: tosca.relationships.Root
valid_target_types: [ tosca.capabilities.xos.Service ]
+ tosca.relationships.UsedByService:
+ derived_from: tosca.relationships.Root
+ valid_target_types: [ tosca.capabilities.xos.Service ]
+
tosca.relationships.ControllerDeployment:
derived_from: tosca.relationships.Root
valid_target_types: [ tosca.capabilities.xos.Deployment ]
diff --git a/xos/tosca/resources/controller.py b/xos/tosca/resources/controller.py
index da4ed64..9a20ea5 100644
--- a/xos/tosca/resources/controller.py
+++ b/xos/tosca/resources/controller.py
@@ -44,6 +44,10 @@
if obj.controllersite.exists():
self.info("Controller %s has active sites; skipping delete" % obj.name)
return
+ for sd in obj.sitedeployments.all():
+ if sd.nodes.exists():
+ self.info("Controller %s has active nodes; skipping delete" % obj.name)
+ return
super(XOSController, self).delete(obj)
diff --git a/xos/tosca/resources/deployment.py b/xos/tosca/resources/deployment.py
index 152b1f9..ed6734c 100644
--- a/xos/tosca/resources/deployment.py
+++ b/xos/tosca/resources/deployment.py
@@ -51,13 +51,13 @@
self.postprocess_privileges(DeploymentRole, DeploymentPrivilege, rolemap, obj, "deployment")
def delete(self, obj):
- if self.get_property("no-delete"):
- self.info("Deployment %s is marked no-delete")
- return
-
if obj.sites.exists():
self.info("Deployment %s has active sites; skipping delete" % obj.name)
return
+ for sd in obj.sitedeployments.all():
+ if sd.nodes.exists():
+ self.info("Deployment %s has active nodes; skipping delete" % obj.name)
+ return
#if obj.nodes.exists():
# self.info("Deployment %s has active nodes; skipping delete" % obj.name)
# return
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
new file mode 100644
index 0000000..03c4eb5
--- /dev/null
+++ b/xos/tosca/resources/onosapp.py
@@ -0,0 +1,63 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+import pdb
+
+from core.models import User, TenantAttribute, Service
+from services.onos.models import ONOSApp, ONOSService
+
+from xosresource import XOSResource
+
+class XOSONOSApp(XOSResource):
+ provides = ["tosca.nodes.ONOSApp", "tosca.nodes.ONOSvBNGApp"]
+ xos_model = ONOSApp
+ copyin_props = ["service_specific_id", "dependencies"]
+
+ def get_xos_args(self, throw_exception=True):
+ args = super(XOSONOSApp, self).get_xos_args()
+
+ # provider_service is mandatory and must be the ONOS Service
+ provider_name = self.get_requirement("tosca.relationships.TenantOfService", throw_exception=throw_exception)
+ if provider_name:
+ args["provider_service"] = self.get_xos_object(ONOSService, throw_exception=throw_exception, name=provider_name)
+
+ # subscriber_service is optional and can be any service
+ subscriber_name = self.get_requirement("tosca.relationships.UsedByService", throw_exception=False)
+ if subscriber_name:
+ args["subscriber_service"] = self.get_xos_object(Service, throw_exception=throw_exception, name=subscriber_name)
+
+ return args
+
+ def get_existing_objs(self):
+ objs = ONOSApp.get_tenant_objects().all()
+ objs = [x for x in objs if x.name == self.nodetemplate.name]
+ return objs
+
+ def set_tenant_attr(self, obj, prop_name, value):
+ value = self.try_intrinsic_function(value)
+ if value:
+ attrs = TenantAttribute.objects.filter(tenant=obj, name=prop_name)
+ if attrs:
+ attr = attrs[0]
+ if attr.value != value:
+ self.info("updating attribute %s" % k)
+ attrs.value = value
+ attrs.save()
+ else:
+ self.info("adding attribute %s" % prop_name)
+ ta = TenantAttribute(tenant=obj, name=prop_name, value=value)
+ ta.save()
+
+ def postprocess(self, obj):
+ props = self.nodetemplate.get_properties()
+ for (k,d) in props.items():
+ v = d.value
+ if k.startswith("config_"):
+ self.set_tenant_attr(obj, k, v)
+
+ def can_delete(self, obj):
+ return super(XOSONOSApp, self).can_delete(obj)
+
diff --git a/xos/tosca/resources/onosservice.py b/xos/tosca/resources/onosservice.py
new file mode 100644
index 0000000..1275fab
--- /dev/null
+++ b/xos/tosca/resources/onosservice.py
@@ -0,0 +1,16 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from services.onos.models import ONOSService
+
+from service import XOSService
+
+class XOSONOSService(XOSService):
+ provides = "tosca.nodes.ONOSService"
+ xos_model = ONOSService
+ copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber"]
+
diff --git a/xos/tosca/resources/xosresource.py b/xos/tosca/resources/xosresource.py
index 62f18db..3553ab1 100644
--- a/xos/tosca/resources/xosresource.py
+++ b/xos/tosca/resources/xosresource.py
@@ -60,6 +60,12 @@
def get_property(self, name):
return self.nodetemplate.get_property_value(name)
+ def get_property_default(self, name, default=None):
+ props = self.nodetemplate.get_properties()
+ if props and name in props.keys():
+ return props[name].value
+ return default
+
def get_xos_object(self, cls, throw_exception=True, **kwargs):
objs = cls.objects.filter(**kwargs)
if not objs:
@@ -80,12 +86,21 @@
def create_or_update(self):
existing_objs = self.get_existing_objs()
if existing_objs:
- self.info("%s %s already exists" % (self.get_model_class_name(), self.nodetemplate.name))
- self.update(existing_objs[0])
+ if self.get_property_default("no-update", False):
+ self.info("%s %s already exists. Skipping update due to 'no-update' property" % (self.get_model_class_name(), self.nodetemplate.name))
+ else:
+ self.info("%s %s already exists" % (self.get_model_class_name(), self.nodetemplate.name))
+ self.update(existing_objs[0])
else:
- self.create()
+ if self.get_property_default("no-create", False):
+ self.info("%s %s does not exist, but 'no-create' is specified" % (self.get_model_class_name(), self.nodetemplate.name))
+ else:
+ self.create()
def can_delete(self, obj):
+ if self.get_property_default("no-delete",False):
+ self.info("%s %s is marked 'no-delete'. Skipping delete." % (self.get_model_class_name(), self.nodetemplate.name))
+ return False
return True
def postprocess_privileges(self, roleclass, privclass, rolemap, obj, toFieldName):
diff --git a/xos/tosca/samples/onos.yaml b/xos/tosca/samples/onos.yaml
new file mode 100644
index 0000000..fc6a3d5
--- /dev/null
+++ b/xos/tosca/samples/onos.yaml
@@ -0,0 +1,94 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup CORD-related services -- vOLT, vCPE, vBNG.
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ ONOS:
+ type: tosca.nodes.ONOSService
+ requirements:
+ properties:
+ kind: onos
+ view_url: /admin/onos/onosservice/$id$/
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ artifacts:
+ pubkey: /opt/xos/observers/onos/onos_key.pub
+
+ vBNG:
+ type: tosca.nodes.ONOSvBNGApp
+ requirements:
+ - onos_tenant:
+ node: ONOS
+ relationship: tosca.relationships.TenantOfService
+ properties:
+ dependencies: org.onosproject.proxyarp, org.onosproject.virtualbng, org.onosproject.openflow, org.onosproject.fwd
+ config_addresses.json: >
+ {
+ "addresses" : [
+ {
+ "dpid" : "00:00:00:00:00:00:00:a1",
+ "port" : "2",
+ "ips" : ["192.0.0.1/24"],
+ "mac" : "00:00:00:00:00:99"
+
+ },
+ {
+ "dpid" : "00:00:00:00:00:00:00:a5",
+ "port" : "4",
+ "ips" : ["200.0.0.5/24"],
+ "mac" : "00:00:00:00:00:98"
+ }
+ ]
+ }
+ config_virtualbng.json: >
+ {
+ "localPublicIpPrefixes" : [
+ "200.0.0.0/32",
+ "201.0.0.0/30",
+ "202.0.0.0/30"
+ ],
+ "nextHopIpAddress" : "200.0.0.5",
+ "publicFacingMac" : "00:00:00:00:00:66",
+ "xosIpAddress" : "10.11.10.1",
+ "xosRestPort" : "9999"
+ }
+
+ mysite:
+ type: tosca.nodes.Site
+
+ mysite_onos:
+ description: ONOS Controller Slice
+ type: tosca.nodes.Slice
+ requirements:
+ - ONOS:
+ node: ONOS
+ relationship: tosca.relationships.MemberOfService
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+
+ my_server:
+ 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_onos
+ relationship: tosca.relationships.MemberOfSlice
+
diff --git a/xos/tosca/tests/controllertest.py b/xos/tosca/tests/controllertest.py
index 5746ada..2b7ba55 100644
--- a/xos/tosca/tests/controllertest.py
+++ b/xos/tosca/tests/controllertest.py
@@ -5,7 +5,9 @@
class ControllerTest(BaseToscaTest):
tests = ["create_controller_minimal",
"create_controller_maximal",
- "destroy_controller"]
+ "create_controller_nocreate",
+ "destroy_controller",
+ "destroy_controller_nodelete"]
def cleanup(self):
self.try_to_delete(Controller, name="testcon")
@@ -53,6 +55,45 @@
domain="mydomain",
deployment=dep)
+ def create_controller_nocreate(self):
+ self.assert_noobj(Controller, "testcon")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testcon", "tosca.nodes.Controller",
+ reqs=[("testdep", "tosca.relationships.ControllerDeployment")],
+ props={"no-create": True}))
+ dep = self.assert_obj(Deployment, "testdep")
+ self.assert_noobj(Controller, "testcon")
+
+ def update_controller(self):
+ self.assert_noobj(Controller, "testcon")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testcon", "tosca.nodes.Controller",
+ reqs=[("testdep", "tosca.relationships.ControllerDeployment")]))
+ dep = self.assert_obj(Deployment, "testdep")
+ orig_con = self.assert_obj(Controller, "testcon",
+ backend_type="",
+ version="",
+ auth_url=None,
+ admin_user=None,
+ admin_password=None,
+ admin_tenant=None,
+ domain=None,
+ deployment=dep)
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testcon", "tosca.nodes.Controller",
+ reqs=[("testdep", "tosca.relationships.ControllerDeployment")],
+ props={"version": "1.1"}))
+ con = self.assert_obj(Controller, "testcon",
+ backend_type="",
+ version="1.1",
+ auth_url=None,
+ admin_user=None,
+ admin_password=None,
+ admin_tenant=None,
+ domain=None,
+ deployment=dep)
+ assert(orig_con.id == con.id)
+
def destroy_controller(self):
self.assert_noobj(Controller, "testcon")
self.execute(self.get_base_templates() +
@@ -64,6 +105,24 @@
reqs=[("testdep", "tosca.relationships.ControllerDeployment")]))
self.assert_noobj(Controller, "testcon")
+ def destroy_controller_nodelete(self):
+ self.assert_noobj(Controller, "testcon")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testcon", "tosca.nodes.Controller",
+ reqs=[("testdep", "tosca.relationships.ControllerDeployment")]))
+ orig_con = self.assert_obj(Controller, "testcon")
+ # NOTE: Had to specify no-delete on the deployment as well, otherwise
+ # the deployment deletion would cause the controller to be deleted
+ # as well. I'm thinking this is as it should be, but it's a little
+ # counter-inutitive.
+ self.destroy(self.make_nodetemplate("testdep", "tosca.nodes.Deployment",
+ props={"no-delete": True}) +
+ self.make_nodetemplate("testcon", "tosca.nodes.Controller",
+ reqs=[("testdep", "tosca.relationships.ControllerDeployment")],
+ props={"no-delete": True}))
+ con = self.assert_obj(Controller, "testcon")
+ assert(orig_con.id == con.id)
+
if __name__ == "__main__":
ControllerTest()
diff --git a/xos/tosca/tests/deploymenttest.py b/xos/tosca/tests/deploymenttest.py
index 0156613..91caf75 100644
--- a/xos/tosca/tests/deploymenttest.py
+++ b/xos/tosca/tests/deploymenttest.py
@@ -10,6 +10,9 @@
"create_deployment_one_image",
"create_deployment_two_images",
"create_deployment_privilege",
+ "create_deployment_nocreate",
+ "update_deployment",
+ "update_deployment_noupdate",
"destroy_deployment",
"destroy_deployment_nodelete"
]
@@ -88,6 +91,35 @@
dps = DeploymentPrivilege.objects.filter(user=user, deployment=dep)
assert(len(dps) == 1)
+ def create_deployment_nocreate(self):
+ self.assert_noobj(Deployment, "testdep")
+ self.execute(self.make_nodetemplate("testdep", "tosca.nodes.Deployment",
+ props={"no-create": True}))
+ self.assert_noobj(Deployment, "testdep")
+
+ def update_deployment(self):
+ self.assert_noobj(Deployment, "testdep")
+ self.execute(self.make_nodetemplate("testdep", "tosca.nodes.Deployment"))
+ orig_dep = self.assert_obj(Deployment, "testdep",
+ accessControl="allow all")
+ self.execute(self.make_nodetemplate("testdep", "tosca.nodes.Deployment",
+ props={"accessControl": "allow padmin@vicci.org"}))
+ dep = self.assert_obj(Deployment, "testdep",
+ accessControl="allow padmin@vicci.org")
+ assert(dep.id == orig_dep.id)
+
+ def update_deployment_noupdate(self):
+ self.assert_noobj(Deployment, "testdep")
+ self.execute(self.make_nodetemplate("testdep", "tosca.nodes.Deployment"))
+ orig_dep = self.assert_obj(Deployment, "testdep",
+ accessControl="allow all")
+ self.execute(self.make_nodetemplate("testdep", "tosca.nodes.Deployment",
+ props={"accessControl": "allow padmin@vicci.org",
+ "no-update": True}))
+ dep = self.assert_obj(Deployment, "testdep",
+ accessControl="allow all")
+ assert(dep.id == orig_dep.id)
+
def destroy_deployment(self):
self.assert_noobj(Deployment, "testdep")
self.execute(self.make_nodetemplate("testdep", "tosca.nodes.Deployment"))
diff --git a/xos/tosca/tests/nodetest.py b/xos/tosca/tests/nodetest.py
index d49dab1..76c56a8 100644
--- a/xos/tosca/tests/nodetest.py
+++ b/xos/tosca/tests/nodetest.py
@@ -4,7 +4,9 @@
class NodeTest(BaseToscaTest):
tests = ["create_node_minimal",
+ "create_node_nocreate",
"destroy_node",
+ "destroy_node_nodelete",
]
def cleanup(self):
@@ -43,7 +45,18 @@
self.make_nodetemplate("testnode", "tosca.nodes.Node",
reqs=[("testsite", "tosca.relationships.MemberOfSite"),
("testdep", "tosca.relationships.MemberOfDeployment")]))
- self.assert_obj(Node, "testnode")
+ node = self.assert_obj(Node, "testnode")
+ assert(node.site_deployment is not None)
+ assert(node.site is not None)
+
+ def create_node_nocreate(self):
+ self.assert_noobj(Node, "testnode")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testnode", "tosca.nodes.Node",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite"),
+ ("testdep", "tosca.relationships.MemberOfDeployment")],
+ props={"no-create": True}))
+ self.assert_noobj(Node, "testnode")
def destroy_node(self):
self.assert_noobj(Node, "testnode")
@@ -58,6 +71,20 @@
("testdep", "tosca.relationships.MemberOfDeployment")]))
self.assert_noobj(Node, "testnode")
+ def destroy_node_nodelete(self):
+ self.assert_noobj(Node, "testnode")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testnode", "tosca.nodes.Node",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite"),
+ ("testdep", "tosca.relationships.MemberOfDeployment")]))
+ self.assert_obj(Node, "testnode")
+ self.destroy(self.get_base_templates() +
+ self.make_nodetemplate("testnode", "tosca.nodes.Node",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite"),
+ ("testdep", "tosca.relationships.MemberOfDeployment")],
+ props={"no-delete": True}))
+ self.assert_obj(Node, "testnode")
+
if __name__ == "__main__":
NodeTest()
diff --git a/xos/tosca/tests/sitetest.py b/xos/tosca/tests/sitetest.py
index c9a4743..5321159 100644
--- a/xos/tosca/tests/sitetest.py
+++ b/xos/tosca/tests/sitetest.py
@@ -7,7 +7,11 @@
"create_site_privilege_tech",
"create_site_privilege_admin",
"create_site_privilege_pi",
+ "create_site_nocreate",
+ "update_site",
+ "update_site_noupdate",
"destroy_site",
+ "destroy_site_nodelete"
]
def cleanup(self):
@@ -60,6 +64,31 @@
assert(len(sps) == 1)
assert(sps[0].role.role == "pi")
+ def create_site_nocreate(self):
+ self.assert_noobj(Site, "testsite")
+ self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site",
+ props={"no-create": True}))
+ site = self.assert_noobj(Site, "testsite")
+
+ def update_site(self):
+ self.assert_noobj(Site, "testsite")
+ self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site"))
+ orig_site = self.assert_obj(Site, "testsite", site_url=None)
+ self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site",
+ props={"site_url": "http://foo.com/"}))
+ site = self.assert_obj(Site, "testsite", site_url="http://foo.com/")
+ assert(orig_site.id == site.id)
+
+ def update_site_noupdate(self):
+ self.assert_noobj(Site, "testsite")
+ self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site"))
+ orig_site = self.assert_obj(Site, "testsite", site_url=None)
+ self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site",
+ props={"site_url": "http://foo.com/",
+ "no-update": True}))
+ site = self.assert_obj(Site, "testsite", site_url=None)
+ assert(orig_site.id == site.id)
+
def destroy_site(self):
self.assert_noobj(Site, "testsite")
self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site"))
@@ -67,6 +96,14 @@
self.destroy(self.make_nodetemplate("testsite", "tosca.nodes.Site"))
self.assert_noobj(Site, "testsite")
+ def destroy_site_nodelete(self):
+ self.assert_noobj(Site, "testsite")
+ self.execute(self.make_nodetemplate("testsite", "tosca.nodes.Site"))
+ site = self.assert_obj(Site, "testsite")
+ self.destroy(self.make_nodetemplate("testsite", "tosca.nodes.Site",
+ props={"no-delete": True}))
+ self.assert_obj(Site, "testsite")
+
if __name__ == "__main__":
SiteTest()
diff --git a/xos/tosca/tests/slicetest.py b/xos/tosca/tests/slicetest.py
index 315f862..98de4e6 100644
--- a/xos/tosca/tests/slicetest.py
+++ b/xos/tosca/tests/slicetest.py
@@ -6,7 +6,11 @@
tests = ["create_slice_minimal",
"create_slice_maximal",
"create_slice_privilege",
- "destroy_slice"]
+ "create_slice_nocreate",
+ "update_slice",
+ "update_slice_noupdate",
+ "destroy_slice",
+ "destroy_slice_nodelete"]
def cleanup(self):
self.try_to_delete(Slice, name="testsite_testslice")
@@ -43,6 +47,41 @@
dps = SlicePrivilege.objects.filter(user=user, slice=slice)
assert(len(dps) == 1)
+ def create_slice_nocreate(self):
+ self.assert_noobj(Slice, "testsite_testslice")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")],
+ props={"no-create": True}))
+ self.assert_noobj(Slice, "testsite_testslice")
+
+ def update_slice(self):
+ self.assert_noobj(Slice, "testsite_testslice")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")]))
+ orig_slice = self.assert_obj(Slice, "testsite_testslice", enabled=True, description="", slice_url="", max_instances=10)
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")],
+ props={"description": "foo"}))
+ slice = self.assert_obj(Slice, "testsite_testslice", enabled=True, description="foo", slice_url="", max_instances=10)
+ assert(orig_slice.id == slice.id)
+
+ def update_slice_noupdate(self):
+ self.assert_noobj(Slice, "testsite_testslice")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")]))
+ orig_slice = self.assert_obj(Slice, "testsite_testslice", enabled=True, description="", slice_url="", max_instances=10)
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")],
+ props={"description": "foo",
+ "no-update": True}))
+ slice = self.assert_obj(Slice, "testsite_testslice", enabled=True, description="", slice_url="", max_instances=10)
+ assert(orig_slice.id == slice.id)
+
def destroy_slice(self):
self.assert_noobj(Slice, "testsite_testslice")
self.execute(self.get_base_templates() +
@@ -54,6 +93,19 @@
reqs=[("testsite", "tosca.relationships.MemberOfSite")]))
self.assert_noobj(Slice, "testsite_testslice")
+ def destroy_slice_nodelete(self):
+ self.assert_noobj(Slice, "testsite_testslice")
+ self.execute(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")]))
+ orig_slice = self.assert_obj(Slice, "testsite_testslice", enabled=True, description="", slice_url="", max_instances=10)
+ self.destroy(self.get_base_templates() +
+ self.make_nodetemplate("testsite_testslice", "tosca.nodes.Slice",
+ reqs=[("testsite", "tosca.relationships.MemberOfSite")],
+ props={"no-delete": True}))
+ slice = self.assert_obj(Slice, "testsite_testslice", enabled=True, description="", slice_url="", max_instances=10)
+ assert(slice.id == orig_slice.id)
+
if __name__ == "__main__":
SliceTest()
diff --git a/xos/xos/hpcapi.py b/xos/xos/hpcapi.py
new file mode 100644
index 0000000..389238e
--- /dev/null
+++ b/xos/xos/hpcapi.py
@@ -0,0 +1,1000 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from rest_framework.generics import GenericAPIView
+from hpc.models import *
+from django.forms import widgets
+from rest_framework import filters
+from django.conf.urls import patterns, url
+from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
+from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
+from apibase import XOSRetrieveUpdateDestroyAPIView, XOSListCreateAPIView, XOSNotAuthenticated
+
+if hasattr(serializers, "ReadOnlyField"):
+ # rest_framework 3.x
+ IdField = serializers.ReadOnlyField
+else:
+ # rest_framework 2.x
+ IdField = serializers.Field
+
+"""
+ Schema of the generator object:
+ all: Set of all Model objects
+ all_if(regex): Set of Model objects that match regex
+
+ Model object:
+ plural: English plural of object name
+ camel: CamelCase version of object name
+ refs: list of references to other Model objects
+ props: list of properties minus refs
+
+ TODO: Deal with subnets
+"""
+
+def get_hpc_REST_patterns():
+ return patterns('',
+ url(r'^hpcapi/$', hpc_api_root),
+
+ url(r'hpcapi/services/$', ServiceList.as_view(), name='service-list'),
+ url(r'hpcapi/services/(?P<pk>[a-zA-Z0-9\-]+)/$', ServiceDetail.as_view(), name ='service-detail'),
+
+ url(r'hpcapi/hpchealthchecks/$', HpcHealthCheckList.as_view(), name='hpchealthcheck-list'),
+ url(r'hpcapi/hpchealthchecks/(?P<pk>[a-zA-Z0-9\-]+)/$', HpcHealthCheckDetail.as_view(), name ='hpchealthcheck-detail'),
+
+ url(r'hpcapi/hpcservices/$', HpcServiceList.as_view(), name='hpcservice-list'),
+ url(r'hpcapi/hpcservices/(?P<pk>[a-zA-Z0-9\-]+)/$', HpcServiceDetail.as_view(), name ='hpcservice-detail'),
+
+ url(r'hpcapi/originservers/$', OriginServerList.as_view(), name='originserver-list'),
+ url(r'hpcapi/originservers/(?P<pk>[a-zA-Z0-9\-]+)/$', OriginServerDetail.as_view(), name ='originserver-detail'),
+
+ url(r'hpcapi/cdnprefixs/$', CDNPrefixList.as_view(), name='cdnprefix-list'),
+ url(r'hpcapi/cdnprefixs/(?P<pk>[a-zA-Z0-9\-]+)/$', CDNPrefixDetail.as_view(), name ='cdnprefix-detail'),
+
+ url(r'hpcapi/users/$', UserList.as_view(), name='user-list'),
+ url(r'hpcapi/users/(?P<pk>[a-zA-Z0-9\-]+)/$', UserDetail.as_view(), name ='user-detail'),
+
+ url(r'hpcapi/serviceproviders/$', ServiceProviderList.as_view(), name='serviceprovider-list'),
+ url(r'hpcapi/serviceproviders/(?P<pk>[a-zA-Z0-9\-]+)/$', ServiceProviderDetail.as_view(), name ='serviceprovider-detail'),
+
+ url(r'hpcapi/contentproviders/$', ContentProviderList.as_view(), name='contentprovider-list'),
+ url(r'hpcapi/contentproviders/(?P<pk>[a-zA-Z0-9\-]+)/$', ContentProviderDetail.as_view(), name ='contentprovider-detail'),
+
+ url(r'hpcapi/accessmaps/$', AccessMapList.as_view(), name='accessmap-list'),
+ url(r'hpcapi/accessmaps/(?P<pk>[a-zA-Z0-9\-]+)/$', AccessMapDetail.as_view(), name ='accessmap-detail'),
+
+ url(r'hpcapi/sitemaps/$', SiteMapList.as_view(), name='sitemap-list'),
+ url(r'hpcapi/sitemaps/(?P<pk>[a-zA-Z0-9\-]+)/$', SiteMapDetail.as_view(), name ='sitemap-detail'),
+
+ )
+
+@api_view(['GET'])
+def hpc_api_root(request, format=None):
+ return Response({
+ 'services': reverse('service-list', request=request, format=format),
+ 'hpchealthchecks': reverse('hpchealthcheck-list', request=request, format=format),
+ 'hpcservices': reverse('hpcservice-list', request=request, format=format),
+ 'originservers': reverse('originserver-list', request=request, format=format),
+ 'cdnprefixs': reverse('cdnprefix-list', request=request, format=format),
+ 'users': reverse('user-list', request=request, format=format),
+ 'serviceproviders': reverse('serviceprovider-list', request=request, format=format),
+ 'contentproviders': reverse('contentprovider-list', request=request, format=format),
+ 'accessmaps': reverse('accessmap-list', request=request, format=format),
+ 'sitemaps': reverse('sitemap-list', request=request, format=format),
+
+ })
+
+# Based on serializers.py
+
+class XOSModelSerializer(serializers.ModelSerializer):
+ def save_object(self, obj, **kwargs):
+
+ """ rest_framework can't deal with ManyToMany relations that have a
+ through table. In xos, most of the through tables we have
+ use defaults or blank fields, so there's no reason why we shouldn't
+ be able to save these objects.
+
+ So, let's strip out these m2m relations, and deal with them ourself.
+ """
+ obj._complex_m2m_data={};
+ if getattr(obj, '_m2m_data', None):
+ for relatedObject in obj._meta.get_all_related_many_to_many_objects():
+ if (relatedObject.field.rel.through._meta.auto_created):
+ # These are non-trough ManyToMany relations and
+ # can be updated just fine
+ continue
+ fieldName = relatedObject.get_accessor_name()
+ if fieldName in obj._m2m_data.keys():
+ obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
+ del obj._m2m_data[fieldName]
+
+ serializers.ModelSerializer.save_object(self, obj, **kwargs);
+
+ for (accessor, stuff) in obj._complex_m2m_data.items():
+ (relatedObject, data) = stuff
+ through = relatedObject.field.rel.through
+ local_fieldName = relatedObject.field.m2m_reverse_field_name()
+ remote_fieldName = relatedObject.field.m2m_field_name()
+
+ # get the current set of existing relations
+ existing = through.objects.filter(**{local_fieldName: obj});
+
+ data_ids = [item.id for item in data]
+ existing_ids = [getattr(item,remote_fieldName).id for item in existing]
+
+ #print "data_ids", data_ids
+ #print "existing_ids", existing_ids
+
+ # remove relations that are in 'existing' but not in 'data'
+ for item in list(existing):
+ if (getattr(item,remote_fieldName).id not in data_ids):
+ print "delete", getattr(item,remote_fieldName)
+ item.delete() #(purge=True)
+
+ # add relations that are in 'data' but not in 'existing'
+ for item in data:
+ if (item.id not in existing_ids):
+ #print "add", item
+ newModel = through(**{local_fieldName: obj, remote_fieldName: item})
+ newModel.save()
+
+
+
+class ServiceSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = Service
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','description','enabled','kind','name','versionNumber','published','view_url','icon_url','public_key','service_specific_id','service_specific_attribute',)
+
+class ServiceIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = Service
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','description','enabled','kind','name','versionNumber','published','view_url','icon_url','public_key','service_specific_id','service_specific_attribute',)
+
+
+
+
+class HpcHealthCheckSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = HpcHealthCheck
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','hpcService','kind','resource_name','result_contains','result_min_size','result_max_size',)
+
+class HpcHealthCheckIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = HpcHealthCheck
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','hpcService','kind','resource_name','result_contains','result_min_size','result_max_size',)
+
+
+
+
+class HpcServiceSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = HpcService
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','description','enabled','kind','name','versionNumber','published','view_url','icon_url','public_key','service_specific_id','service_specific_attribute','service_ptr','cmi_hostname','hpc_port80','watcher_hpc_network','watcher_dnsdemux_network','watcher_dnsredir_network',)
+
+class HpcServiceIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = HpcService
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','description','enabled','kind','name','versionNumber','published','view_url','icon_url','public_key','service_specific_id','service_specific_attribute','service_ptr','cmi_hostname','hpc_port80','watcher_hpc_network','watcher_dnsdemux_network','watcher_dnsredir_network',)
+
+
+
+
+class OriginServerSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = OriginServer
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','origin_server_id','url','contentProvider','authenticated','enabled','protocol','redirects','description',)
+
+class OriginServerIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = OriginServer
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','origin_server_id','url','contentProvider','authenticated','enabled','protocol','redirects','description',)
+
+
+
+
+class CDNPrefixSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = CDNPrefix
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','cdn_prefix_id','prefix','contentProvider','description','defaultOriginServer','enabled',)
+
+class CDNPrefixIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = CDNPrefix
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','cdn_prefix_id','prefix','contentProvider','description','defaultOriginServer','enabled',)
+
+
+
+
+class UserSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+
+ contentproviders = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='contentprovider-detail')
+
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = User
+ fields = ('humanReadableName', 'validators', 'id','password','last_login','email','username','firstname','lastname','phone','user_url','site','public_key','is_active','is_admin','is_staff','is_readonly','is_registering','is_appuser','login_page','created','updated','enacted','policed','backend_status','deleted','write_protect','timezone','contentproviders',)
+
+class UserIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+
+ contentproviders = serializers.PrimaryKeyRelatedField(many=True, queryset = ContentProvider.objects.all())
+
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = User
+ fields = ('humanReadableName', 'validators', 'id','password','last_login','email','username','firstname','lastname','phone','user_url','site','public_key','is_active','is_admin','is_staff','is_readonly','is_registering','is_appuser','login_page','created','updated','enacted','policed','backend_status','deleted','write_protect','timezone','contentproviders',)
+
+
+
+
+class ServiceProviderSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = ServiceProvider
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','hpcService','service_provider_id','name','description','enabled',)
+
+class ServiceProviderIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = ServiceProvider
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','hpcService','service_provider_id','name','description','enabled',)
+
+
+
+
+class ContentProviderSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = ContentProvider
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','content_provider_id','name','enabled','description','serviceProvider',)
+
+class ContentProviderIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = ContentProvider
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','content_provider_id','name','enabled','description','serviceProvider',)
+
+
+
+
+class AccessMapSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = AccessMap
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','contentProvider','name','description','map',)
+
+class AccessMapIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = AccessMap
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','contentProvider','name','description','map',)
+
+
+
+
+class SiteMapSerializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = SiteMap
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','contentProvider','serviceProvider','cdnPrefix','hpcService','name','description','map','map_id',)
+
+class SiteMapIdSerializer(XOSModelSerializer):
+ id = IdField()
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = SiteMap
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','contentProvider','serviceProvider','cdnPrefix','hpcService','name','description','map','map_id',)
+
+
+
+
+serializerLookUp = {
+
+ Service: ServiceSerializer,
+
+ HpcHealthCheck: HpcHealthCheckSerializer,
+
+ HpcService: HpcServiceSerializer,
+
+ OriginServer: OriginServerSerializer,
+
+ CDNPrefix: CDNPrefixSerializer,
+
+ User: UserSerializer,
+
+ ServiceProvider: ServiceProviderSerializer,
+
+ ContentProvider: ContentProviderSerializer,
+
+ AccessMap: AccessMapSerializer,
+
+ SiteMap: SiteMapSerializer,
+
+ None: None,
+ }
+
+# Based on core/views/*.py
+
+
+class ServiceList(XOSListCreateAPIView):
+ queryset = Service.objects.select_related().all()
+ serializer_class = ServiceSerializer
+ id_serializer_class = ServiceIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','description','enabled','kind','name','versionNumber','published','view_url','icon_url','public_key','service_specific_id','service_specific_attribute',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return Service.select_by_user(self.request.user)
+
+
+class ServiceDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = Service.objects.select_related().all()
+ serializer_class = ServiceSerializer
+ id_serializer_class = ServiceIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return Service.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class HpcHealthCheckList(XOSListCreateAPIView):
+ queryset = HpcHealthCheck.objects.select_related().all()
+ serializer_class = HpcHealthCheckSerializer
+ id_serializer_class = HpcHealthCheckIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','hpcService','kind','resource_name','result_contains','result_min_size','result_max_size',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return HpcHealthCheck.select_by_user(self.request.user)
+
+
+class HpcHealthCheckDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = HpcHealthCheck.objects.select_related().all()
+ serializer_class = HpcHealthCheckSerializer
+ id_serializer_class = HpcHealthCheckIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return HpcHealthCheck.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class HpcServiceList(XOSListCreateAPIView):
+ queryset = HpcService.objects.select_related().all()
+ serializer_class = HpcServiceSerializer
+ id_serializer_class = HpcServiceIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','description','enabled','kind','name','versionNumber','published','view_url','icon_url','public_key','service_specific_id','service_specific_attribute','service_ptr','cmi_hostname','hpc_port80','watcher_hpc_network','watcher_dnsdemux_network','watcher_dnsredir_network',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return HpcService.select_by_user(self.request.user)
+
+
+class HpcServiceDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = HpcService.objects.select_related().all()
+ serializer_class = HpcServiceSerializer
+ id_serializer_class = HpcServiceIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return HpcService.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class OriginServerList(XOSListCreateAPIView):
+ queryset = OriginServer.objects.select_related().all()
+ serializer_class = OriginServerSerializer
+ id_serializer_class = OriginServerIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','origin_server_id','url','contentProvider','authenticated','enabled','protocol','redirects','description',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return OriginServer.select_by_user(self.request.user)
+
+
+class OriginServerDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = OriginServer.objects.select_related().all()
+ serializer_class = OriginServerSerializer
+ id_serializer_class = OriginServerIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return OriginServer.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class CDNPrefixList(XOSListCreateAPIView):
+ queryset = CDNPrefix.objects.select_related().all()
+ serializer_class = CDNPrefixSerializer
+ id_serializer_class = CDNPrefixIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','cdn_prefix_id','prefix','contentProvider','description','defaultOriginServer','enabled',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return CDNPrefix.select_by_user(self.request.user)
+
+
+class CDNPrefixDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = CDNPrefix.objects.select_related().all()
+ serializer_class = CDNPrefixSerializer
+ id_serializer_class = CDNPrefixIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return CDNPrefix.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class UserList(XOSListCreateAPIView):
+ queryset = User.objects.select_related().all()
+ serializer_class = UserSerializer
+ id_serializer_class = UserIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','password','last_login','email','username','firstname','lastname','phone','user_url','site','public_key','is_active','is_admin','is_staff','is_readonly','is_registering','is_appuser','login_page','created','updated','enacted','policed','backend_status','deleted','write_protect','timezone','contentproviders',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return User.select_by_user(self.request.user)
+
+
+class UserDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = User.objects.select_related().all()
+ serializer_class = UserSerializer
+ id_serializer_class = UserIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return User.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class ServiceProviderList(XOSListCreateAPIView):
+ queryset = ServiceProvider.objects.select_related().all()
+ serializer_class = ServiceProviderSerializer
+ id_serializer_class = ServiceProviderIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','hpcService','service_provider_id','name','description','enabled',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return ServiceProvider.select_by_user(self.request.user)
+
+
+class ServiceProviderDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = ServiceProvider.objects.select_related().all()
+ serializer_class = ServiceProviderSerializer
+ id_serializer_class = ServiceProviderIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return ServiceProvider.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class ContentProviderList(XOSListCreateAPIView):
+ queryset = ContentProvider.objects.select_related().all()
+ serializer_class = ContentProviderSerializer
+ id_serializer_class = ContentProviderIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','content_provider_id','name','enabled','description','serviceProvider',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return ContentProvider.select_by_user(self.request.user)
+
+
+class ContentProviderDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = ContentProvider.objects.select_related().all()
+ serializer_class = ContentProviderSerializer
+ id_serializer_class = ContentProviderIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return ContentProvider.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class AccessMapList(XOSListCreateAPIView):
+ queryset = AccessMap.objects.select_related().all()
+ serializer_class = AccessMapSerializer
+ id_serializer_class = AccessMapIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','contentProvider','name','description','map',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return AccessMap.select_by_user(self.request.user)
+
+
+class AccessMapDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = AccessMap.objects.select_related().all()
+ serializer_class = AccessMapSerializer
+ id_serializer_class = AccessMapIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return AccessMap.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
+class SiteMapList(XOSListCreateAPIView):
+ queryset = SiteMap.objects.select_related().all()
+ serializer_class = SiteMapSerializer
+ id_serializer_class = SiteMapIdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','contentProvider','serviceProvider','cdnPrefix','hpcService','name','description','map','map_id',)
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return SiteMap.select_by_user(self.request.user)
+
+
+class SiteMapDetail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = SiteMap.objects.select_related().all()
+ serializer_class = SiteMapSerializer
+ id_serializer_class = SiteMapIdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return SiteMap.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+
+
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index ba5ff1d..e660352 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -148,6 +148,7 @@
'core',
'hpc',
'cord',
+ 'services.onos',
'ceilometer',
'requestrouter',
# 'urlfilter',
diff --git a/xos/xos/urls.py b/xos/xos/urls.py
index 0adf32d..3660282 100644
--- a/xos/xos/urls.py
+++ b/xos/xos/urls.py
@@ -5,9 +5,11 @@
# This is the generated API
from xosapi import *
+from hpcapi import *
from core.views.legacyapi import LegacyXMLRPC
from core.views.services import ServiceGridView, ServiceGraphView
+from helloworld.view import *
#from core.views.analytics import AnalyticsAjaxView
from core.models import *
from rest_framework import generics
@@ -27,6 +29,7 @@
# Examples:
url(r'^stats', 'core.views.stats.Stats', name='stats'),
url(r'^observer', 'core.views.observer.Observer', name='observer'),
+ url(r'^helloworld', HelloWorldView.as_view(), name='helloWorld'),
url(r'^serviceGrid', ServiceGridView.as_view(), name='serviceGrid'),
url(r'^serviceGraph.png', ServiceGraphView.as_view(), name='serviceGraph'),
url(r'^hpcConfig', 'core.views.hpc_config.HpcConfig', name='hpcConfig'),
@@ -54,5 +57,5 @@
# XOSLib rest methods
url(r'^xoslib/', include('core.xoslib.methods', namespace='xoslib')),
- ) + get_REST_patterns()
+ ) + get_REST_patterns() + get_hpc_REST_patterns()
diff --git a/xos/xos/xosapi.py b/xos/xos/xosapi.py
index bec3ea2..6532c72 100644
--- a/xos/xos/xosapi.py
+++ b/xos/xos/xosapi.py
@@ -1806,7 +1806,7 @@
return None
class Meta:
model = Controller
- fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','name','backend_type','version','auth_url','admin_user','admin_password','admin_tenant','domain','deployment','dashboardviews',)
+ fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','name','backend_type','version','auth_url','admin_user','admin_password','admin_tenant','domain','rabbit_host','rabbit_user','rabbit_password','deployment','dashboardviews',)
class ControllerIdSerializer(XOSModelSerializer):
id = IdField()
@@ -4644,7 +4644,7 @@
serializer_class = ControllerSerializer
id_serializer_class = ControllerIdSerializer
filter_backends = (filters.DjangoFilterBackend,)
- filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','name','backend_type','version','auth_url','admin_user','admin_password','admin_tenant','domain','deployment','dashboardviews',)
+ filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_status','deleted','write_protect','lazy_blocked','no_sync','name','backend_type','version','auth_url','admin_user','admin_password','admin_tenant','domain','rabbit_host','rabbit_user','rabbit_password','deployment','dashboardviews',)
def get_serializer_class(self):
no_hyperlinks=False