CORD-1951 fix and cleanup vEG synchronizer

Change-Id: I938edf40da13192614fa16ecf350bd18bea84f8d
(cherry picked from commit 8e56c00757e05a18e059772a5524c982d49781f9)
diff --git a/xos/synchronizer/broadbandshield.py b/xos/synchronizer/broadbandshield.py
deleted file mode 100644
index a0771e7..0000000
--- a/xos/synchronizer/broadbandshield.py
+++ /dev/null
@@ -1,412 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import requests
-import logging
-import json
-import sys
-from rest_framework.exceptions import APIException
-
-""" format of settings
-
-    ["settings"]
-        ["watershed"]
-        ["rating"]
-        ["categories"]
-        ["blocklist"]
-        ["allowlist"]
-
-    ["users"]
-        array
-            ["account_id"] - 58
-            ["reporting"] - False
-            ["name"] - Scott1
-            ["devices"]
-            ["settings"] -
-                ["watershed"]
-                ["rating"]
-                ["categories"]
-                ["blocklist"]
-                ["allowlist"]
-
-    ["devices"]
-        array
-            ["username"] - "Scott1" or "" if whole-house
-            ["uuid"] - empty
-            ["mac_address"] - mac address as hex digits in ascii
-            ["type"] - "laptop"
-            ["name"] - human readable name of device ("Scott's laptop")
-            ["settings"]
-                 ["watershed"]
-                     array
-                         array
-                             ["rating"]
-                             ["category"]
-                 ["rating"] - ["G" | "NONE"]
-                 ["categories"] - list of categories set by rating
-                 ["blocklist"] - []
-                 ["allowlist"] - []
-"""
-
-class BBS_Failure(APIException):
-    status_code=400
-    def __init__(self, why="broadbandshield error", fields={}):
-        APIException.__init__(self, {"error": "BBS_Failure",
-                            "specific_error": why,
-                            "fields": fields})
-
-
-class BBS:
-    level_map = {"PG_13": "PG13",
-                 "NONE": "OFF",
-                 "ALL": "NONE",
-                 None: "NONE"}
-
-    def __init__(self, username, password, bbs_hostname=None, bbs_port=None):
-        self.username = username
-        self.password = password
-
-        # XXX not tested on port 80
-        #self.bbs_hostname = "www.broadbandshield.com"
-        #self.bbs_port = 80
-
-        if not bbs_hostname:
-            bbs_hostname = "cordcompute01.onlab.us"
-        if not bbs_port:
-            bbs_port = 8018
-
-        self.bbs_hostname = bbs_hostname
-        self.bbs_port = int(bbs_port)
-
-        self.api = "http://%s:%d/api" % (self.bbs_hostname, self.bbs_port)
-        self.nic_update = "http://%s:%d/nic/update" % (self.bbs_hostname, self.bbs_port)
-
-        self.session = None
-        self.settings = None
-
-    def login(self):
-        self.session = requests.Session()
-        r = self.session.post(self.api + "/login", data = json.dumps({"email": self.username, "password": self.password}))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to login (%d)" % r.status_code)
-
-    def get_account(self):
-        if not self.session:
-            self.login()
-
-        r = self.session.get(self.api + "/account")
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to get account settings (%d)" % r.status_code)
-        self.settings = r.json()
-
-        return self.settings
-
-    def post_account(self):
-        if not self.settings:
-             raise XOSProgrammingError("no settings to post")
-
-        r = self.session.post(self.api + "/account/settings", data= json.dumps(self.settings))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to set account settings (%d)" % r.status_code)
-
-    def add_device(self, name, mac, type="tablet", username=""):
-        data = {"name": name, "mac_address": mac, "type": type, "username": username}
-        r = self.session.post(self.api + "/device", data = json.dumps(data))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to add device (%d)" % r.status_code)
-
-    def delete_device(self, data):
-        r = self.session.delete(self.api + "/device", data = json.dumps(data))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to delete device (%d)" % r.status_code)
-
-    def add_user(self, name, rating="NONE", categories=[]):
-        data = {"name": name, "settings": {"rating": rating, "categories": categories}}
-        r = self.session.post(self.api + "/users", data = json.dumps(data))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to add user (%d)" % r.status_code)
-
-    def delete_user(self, data):
-        r = self.session.delete(self.api + "/users", data = json.dumps(data))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to delete user (%d)" % r.status_code)
-
-    def clear_users_and_devices(self):
-        if not self.settings:
-            self.get_account()
-
-        for device in self.settings["devices"]:
-            self.delete_device(device)
-
-        for user in self.settings["users"]:
-            self.delete_user(user)
-
-    def get_whole_home_level(self):
-        if not self.settings:
-            self.get_account()
-
-        return self.settings["settings"]["rating"]
-
-    def sync(self, whole_home_level, users):
-        if not self.settings:
-            self.get_account()
-
-        veg_users = {}
-        for user in users:
-            user = user.copy()
-            user["level"] = self.level_map.get(user["level"], user["level"])
-            user["mac"] = user.get("mac", "")
-            veg_users[user["name"]] = user
-
-        whole_home_level = self.level_map.get(whole_home_level, whole_home_level)
-
-        if (whole_home_level != self.settings["settings"]["rating"]):
-            print "*** set whole_home", whole_home_level, "***"
-            self.settings["settings"]["rating"] = whole_home_level
-            self.post_account()
-
-        bbs_usernames = [bbs_user["name"] for bbs_user in self.settings["users"]]
-        bbs_devicenames = [bbs_device["name"] for bbs_device in self.settings["devices"]]
-
-        add_users = []
-        add_devices = []
-        delete_users = []
-        delete_devices = []
-
-        for bbs_user in self.settings["users"]:
-             bbs_username = bbs_user["name"]
-             if bbs_username in veg_users.keys():
-                 veg_user = veg_users[bbs_username]
-                 if bbs_user["settings"]["rating"] != veg_user["level"]:
-                     print "set user", veg_user["name"], "rating", veg_user["level"]
-                     #bbs_user["settings"]["rating"] = veg_user["level"]
-                     # add can be used as an update
-                     add_users.append(veg_user)
-             else:
-                 delete_users.append(bbs_user)
-
-        for bbs_device in self.settings["devices"]:
-             bbs_devicename = bbs_device["name"]
-             if bbs_devicename in veg_users.keys():
-                 veg_user = veg_users[bbs_devicename]
-                 if bbs_device["mac_address"] != veg_user["mac"]:
-                     print "set device", veg_user["name"], "mac", veg_user["mac"]
-                     #bbs_device["mac_address"] = veg_user["mac"]
-                     # add of a device can't be used as an update, as you'll end
-                     # up with two of them.
-                     delete_devices.append(bbs_device)
-                     add_devices.append(veg_user)
-             else:
-                 delete_devices.append(bbs_device)
-
-        for (username, user) in veg_users.iteritems():
-            if not username in bbs_usernames:
-                add_users.append(user)
-            if not username in bbs_devicenames:
-                add_devices.append(user)
-
-        for bbs_user in delete_users:
-            print "delete user", bbs_user["name"]
-            self.delete_user(bbs_user)
-
-        for bbs_device in delete_devices:
-            print "delete device", bbs_device["name"]
-            self.delete_device(bbs_device)
-
-        for veg_user in add_users:
-            print "add user", veg_user["name"], "level", veg_user["level"]
-            self.add_user(veg_user["name"], veg_user["level"])
-
-        for veg_user in add_devices:
-            print "add device", veg_user["name"], "mac", veg_user["mac"]
-            self.add_device(veg_user["name"], veg_user["mac"], "tablet", veg_user["name"])
-
-    def get_whole_home_rating(self):
-        return self.settings["settings"]["rating"]
-
-    def get_user(self, name):
-        for user in self.settings["users"]:
-            if user["name"]==name:
-                return user
-        return None
-
-    def get_device(self, name):
-        for device in self.settings["devices"]:
-             if device["name"]==name:
-                 return device
-        return None
-
-    def dump(self):
-        if not self.settings:
-            self.get_account()
-
-        print "whole_home_rating:", self.settings["settings"]["rating"]
-        print "users:"
-        for user in self.settings["users"]:
-            print "  user", user["name"], "rating", user["settings"]["rating"]
-
-        print "devices:"
-        for device in self.settings["devices"]:
-            print "  device", device["name"], "user", device["username"], "rating", device["settings"]["rating"], "mac", device["mac_address"]
-
-    def associate(self, ip):
-        bbs_hostname = "cordcompute01.onlab.us"
-        r = requests.get(self.nic_update, params={"hostname": "onlab.us"}, headers={"X-Forwarded-For": ip}, auth=requests.auth.HTTPBasicAuth(self.username,self.password))
-        if (r.status_code != 200):
-            raise BBS_Failure("Failed to associate account with ip (%d)" % r.status_code)
-
-def dump():
-    bbs = BBS(sys.argv[2], sys.argv[3])
-    bbs.dump()
-
-def associate():
-    if len(sys.argv)<5:
-        print "you need to specify IP address"
-        sys.exit(-1)
-
-    bbs = BBS(sys.argv[2], sys.argv[3])
-    bbs.associate(sys.argv[4])
-
-def self_test():
-    bbs = BBS(sys.argv[2], sys.argv[3])
-
-    print "*** initial ***"
-    bbs.dump()
-
-    open("bbs.json","w").write(json.dumps(bbs.settings))
-
-    # a new BBS account will throw a 500 error if it has no rating
-    bbs.settings["settings"]["rating"] = "R"
-    #bbs.settings["settings"]["category"] = [u'PORNOGRAPHY', u'ADULT', u'ILLEGAL', u'WEAPONS', u'DRUGS', u'GAMBLING', u'CYBERBULLY', u'ANONYMIZERS', u'SUICIDE', u'MALWARE']
-    #bbs.settings["settings"]["blocklist"] = []
-    #bbs.settings["settings"]["allowlist"] = []
-    #for water in bbs.settings["settings"]["watershed"];
-    #    water["categories"]=[]
-    # delete everything
-    bbs.post_account()
-    bbs.clear_users_and_devices()
-
-    print "*** cleared ***"
-    bbs.settings=None
-    bbs.dump()
-
-    users = [{"name": "Moms pc", "level": "R", "mac": "010203040506"},
-             {"name": "Dads pc", "level": "R", "mac": "010203040507"},
-             {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"},
-             {"name": "Jills iphone", "level": "G", "mac": "010203040509"}]
-
-    print "*** syncing mom-R, Dad-R, jack-PG, Jill-G, wholehome-PG-13 ***"
-
-    bbs.settings = None
-    bbs.sync("PG-13", users)
-
-    print "*** after sync ***"
-    bbs.settings=None
-    bbs.dump()
-    assert(bbs.get_whole_home_rating() == "PG-13")
-    assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
-    assert(bbs.get_user("Dads pc")["settings"]["rating"] == "R")
-    assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
-    assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "G")
-    assert(bbs.get_device("Moms pc")["mac_address"] == "010203040506")
-    assert(bbs.get_device("Dads pc")["mac_address"] == "010203040507")
-    assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
-    assert(bbs.get_device("Jills iphone")["mac_address"] == "010203040509")
-
-    print "*** update whole home level ***"
-    bbs.settings=None
-    bbs.get_account()
-    bbs.settings["settings"]["rating"] = "PG"
-    bbs.post_account()
-
-    print "*** after sync ***"
-    bbs.settings=None
-    bbs.dump()
-    assert(bbs.get_whole_home_rating() == "PG")
-    assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
-    assert(bbs.get_user("Dads pc")["settings"]["rating"] == "R")
-    assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
-    assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "G")
-    assert(bbs.get_device("Moms pc")["mac_address"] == "010203040506")
-    assert(bbs.get_device("Dads pc")["mac_address"] == "010203040507")
-    assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
-    assert(bbs.get_device("Jills iphone")["mac_address"] == "010203040509")
-
-    print "*** delete dad, change moms IP, change jills level to PG, change whole home to PG-13 ***"
-    users = [{"name": "Moms pc", "level": "R", "mac": "010203040511"},
-             {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"},
-             {"name": "Jills iphone", "level": "PG", "mac": "010203040509"}]
-
-    bbs.settings = None
-    bbs.sync("PG-13", users)
-
-    print "*** after sync ***"
-    bbs.settings=None
-    bbs.dump()
-    assert(bbs.get_whole_home_rating() == "PG-13")
-    assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
-    assert(bbs.get_user("Dads pc") == None)
-    assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
-    assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "PG")
-    assert(bbs.get_device("Moms pc")["mac_address"] == "010203040511")
-    assert(bbs.get_device("Dads pc") == None)
-    assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
-
-    print "add dad's laptop"
-    users = [{"name": "Moms pc", "level": "R", "mac": "010203040511"},
-             {"name": "Dads laptop", "level": "PG-13", "mac": "010203040512"},
-             {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"},
-             {"name": "Jills iphone", "level": "PG", "mac": "010203040509"}]
-
-    bbs.settings = None
-    bbs.sync("PG-13", users)
-
-    print "*** after sync ***"
-    bbs.settings=None
-    bbs.dump()
-    assert(bbs.get_whole_home_rating() == "PG-13")
-    assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
-    assert(bbs.get_user("Dads pc") == None)
-    assert(bbs.get_user("Dads laptop")["settings"]["rating"] == "PG-13")
-    assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
-    assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "PG")
-    assert(bbs.get_device("Moms pc")["mac_address"] == "010203040511")
-    assert(bbs.get_device("Dads pc") == None)
-    assert(bbs.get_device("Dads laptop")["mac_address"] == "010203040512")
-    assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
-
-    #bbs.add_user("tom", "G", [u'PORNOGRAPHY', u'ADULT', u'ILLEGAL', u'WEAPONS', u'DRUGS', u'GAMBLING', u'SOCIAL', u'CYBERBULLY', u'GAMES', u'ANONYMIZERS', u'SUICIDE', u'MALWARE'])
-    #bbs.add_device(name="tom's iphone", mac="010203040506", type="tablet", username="tom")
-
-def main():
-    if len(sys.argv)<4:
-        print "syntax: broadbandshield.py <operation> <email> <password>"
-        print "        operation = [dump | selftest | assocate"
-        sys.exit(-1)
-
-    operation = sys.argv[1]
-
-    if operation=="dump":
-        dump()
-    elif operation=="selftest":
-        self_test()
-    elif operation=="associate":
-        associate()
-
-if __name__ == "__main__":
-    main()
-
-
diff --git a/xos/synchronizer/model_policies/model_policy_vegtenant.py b/xos/synchronizer/model_policies/model_policy_vegtenant.py
index 30b7113..fc2cec5 100644
--- a/xos/synchronizer/model_policies/model_policy_vegtenant.py
+++ b/xos/synchronizer/model_policies/model_policy_vegtenant.py
@@ -15,6 +15,7 @@
 from synchronizers.new_base.modelaccessor import *
 from synchronizers.new_base.model_policies.model_policy_tenantwithcontainer import TenantWithContainerPolicy, LeastLoadedNodeScheduler
 from synchronizers.new_base.exceptions import *
+import wrappers.vegtenant
 
 class VEGTenantPolicy(TenantWithContainerPolicy):
     model_name = "VEGTenant"
@@ -35,12 +36,10 @@
         if tenant.deleted:
             return
 
-<<<<<<< HEAD:xos/synchronizer/model_policies/model_policy_vegtenant.py
         if tenant.vrouter is None:
             vrouter = self.allocate_public_service_instance(address_pool_name="addresses_veg", subscriber_tenant=tenant)
             vrouter.save()
 
-
     def cleanup_orphans(self, tenant):
         # ensure vEG only has one AddressManagerServiceInstance
         cur_asi = tenant.address_service_instance
diff --git a/xos/synchronizer/observer_ansible_test.py b/xos/synchronizer/observer_ansible_test.py
deleted file mode 100644
index 31b663e..0000000
--- a/xos/synchronizer/observer_ansible_test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-#!/usr/bin/env python
-import os
-import sys
-
-sys.path.append("../..")
-import synchronizers.base.ansible_helper
-
-print sys.argv
-
-private_key="""-----BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEAtJiuarud5S4Y2quDeWyaS0UCQGQtfuSzzNhplFwujYnJGL65
-e14REtv+UuHGymyr/SfkTrBd8vH5NI2UZ/4sZW13ieI/1d97OeVe2+ct0Y4BaFEI
-3Hja6DIpsY3Q2cBQsWUwcQzbMIF9jIq8SzwR1zk8UtZi09fNxqjCchRPlBvbiLKX
-g0/yirN237WbaKzK++8EPy3nuv83216MXHFFSjuxfICe/RhjaqMzVp7isSbv1suU
-geyvNNzU71c/K13gTggdcIXeRQBiJYio2Sn3h2nsV6AFqFH4fjERxWG55Q4e3jeE
-tWM/Dw+hqYKg/25UcmM862a6sUmuDCmM5S3VEQIDAQABAoIBACH88iZoNOki6c6N
-pOq/Q7KSxD+2GtHc3PHacNRZHPHKUqxziJjtNS1lddHHaBBEr4GvdkpQ6v2ECLCZ
-TKrdrSFRnsO2bukjbB+TSWz9byQXI7CsP4yuuhQlDK+7zuiMRyN7tcgw8TeJx0Uh
-/xnxrjHhNbcpXeQcoz+WFzI9HFT1MEGmMS4Lyp/zLB/pmfY9h7V9d+EeRZDi78jq
-Vir6MI6iCTa0T02dvHUFOg+wXLb0nb8V1xKDL+6cAJla7LzwoG8lTnvp5DSYCojI
-5JrILYafeO8RbBV2GWmaE5mkHgeBkFZ+qZQ7K0MjR30Yh6tajB7P3+F/Max8FUgW
-xLHr8AECgYEA2+o0ge3HtZcepEFBKKYnLTwoEpPCfLElWZHzUJYDz259s4JLsfak
-tROANFEdsJUjpmWG52MCL+bgKFFOedDkt4p1jgcIneaHk0jvoU11wG7W3jZZVy1q
-WjQNH5vDU+hg5tm/CREwm7lbUxR9Xuj9K63CNAAGp8KO7h2tOH8woIECgYEA0jrb
-LUg30RxO3+vrq9dUYohrDRisk5zKXuRLfxRA+E+ruvZ7CctG2OpM+658/qZM/w95
-7pOj6zz3//w7tAvH9erY+JOISnzaYKx04sYC1MfbFiFkq5j0gpuYm/MULDYNvFqr
-NU2Buj4dW+ZB+SeficsQOqm5QeNxh1kgiDCs7JECgYEAjSLGCAzeesA9vhTTCI95
-3SIaZbHGw9e8rLtqeHGOiHXU3nvksJYmJsAZK3pTn5xXgNbvuVhlcvCtM7LatntG
-DjUiNMB22z+0CuZoRBE+XP3FkF84/yX6d2Goenyw4wzkA8QDQoJxu789yRgBTgQh
-VwLw/AZ4PvoyWMdbAENApgECgYEAvFikosYP09XTyIPaKaOKY5iqqBoSC1GucSOB
-jAG+T3k5dxB6nQS0nYQUomvqak7drqnT6O33Lrr5ySrW5nCjnmvgJZwvv+Rp1bDM
-K5uRT8caPpJ+Wcp4TUdPi3BVA2MOHVDyEJg3AH/D1+DL/IgGQ/JcwOHsKt61iLhO
-EBXj5zECgYEAk+HuwksUPkSxg/AiJGbapGDK6XGymEUzo2duWlnofRqGcZ3NT3bB
-/kDI1KxQdlpODXSi4/BuTpbQiFOrzcEq5e5ytoMxlCHh3Fl3Jxl+JlgO21vAUvP6
-4SET7Q/6LxmfBlCVRg0dXDwcfJLgbnWxyvprIcz4e0FSFVZTBs/6tFk=
------END RSA PRIVATE KEY-----
-"""
-
-observer.ansible.run_template_ssh("test.yaml",
-                                  {"instance_name": "onlab_test405-378",
-                                   "instance_id": "instance-0000004d",
-                                   "hostname": "node67.washington.vicci.org",
-                                   "private_key": private_key})
-
diff --git a/xos/synchronizer/start-bbs.sh b/xos/synchronizer/start-bbs.sh
deleted file mode 100755
index a9fcacb..0000000
--- a/xos/synchronizer/start-bbs.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-#! /bin/bash
-
-# put this in /opt/xerocole/start-bbs.sh
-# make sure it's executable
-# set it up in crontab
-#   @reboot /opt/xerocole/start-bbs.sh
-
-ulimit -n 200000
-cd /opt/xerocole/answerx
-/opt/xerocole/answerx/startStop checkconfig answerx
-/opt/xerocole/answerx/startStop start answerx
-cd /opt/xerocole/namecontrols
-nohup /opt/xerocole/namecontrols/broadbandshield &
-nohup socat TCP-LISTEN:80,bind=0.0.0.0,fork TCP4:127.0.0.1:8018 &  
diff --git a/xos/synchronizer/steps/sync_vegtenant.py b/xos/synchronizer/steps/sync_vegtenant.py
index 6741518..0959073 100644
--- a/xos/synchronizer/steps/sync_vegtenant.py
+++ b/xos/synchronizer/steps/sync_vegtenant.py
@@ -25,6 +25,8 @@
 from synchronizers.new_base.modelaccessor import *
 from synchronizers.new_base.ansible_helper import run_template_ssh
 from xos.logger import Logger, logging
+import wrappers.vegtenant
+import wrappers.veeserviceinstance
 
 # hpclibrary will be in steps/..
 parentdir = os.path.join(os.path.dirname(__file__),"..")
@@ -49,7 +51,7 @@
         if not o.owner:
             return None
 
-        vegs = VEGService.objects.filter(id=o.provider_service.id)
+        vegs = VEGService.objects.filter(id=o.owner.id)
         if not vegs:
             return None
 
@@ -89,7 +91,7 @@
 
         safe_macs=[]
         if veg_service.url_filter_kind == "safebrowsing":
-            if o.volt and o.volt.subscriber:
+            if o.volt and o.volt.subscriber and hasattr(o.volt.subscriber, "devices"):
                 for user in o.volt.subscriber.devices:
                     level = user.get("level",None)
                     mac = user.get("mac",None)
@@ -117,8 +119,18 @@
                 "dns_servers": [x.strip() for x in veg_service.dns_servers.split(",")],
                 "url_filter_kind": veg_service.url_filter_kind }
 
-        # add in the sync_attributes that come from the SubscriberRoot object
+        # Some subscriber models may not implement all fields that we look for, so specify some defaults.
+        fields["firewall_rules"] = ""
+        fields["firewall_enable"] = False
+        fields["url_filter_enable"] = False
+        fields["url_filter_level"] = "PG"
+        fields["cdn_enable"] = False
+        fields["uplink_speed"] = 1000000000
+        fields["downlink_speed"] = 1000000000
+        fields["enable_uverse"] = True
+        fields["status"] = "enabled"
 
+        # add in the sync_attributes that come from the SubscriberRoot object
         if o.volt and o.volt.subscriber and hasattr(o.volt.subscriber, "sync_attributes"):
             for attribute_name in o.volt.subscriber.sync_attributes:
                 fields[attribute_name] = getattr(o.volt.subscriber, attribute_name)
diff --git a/xos/synchronizer/templates/bwlimit.sh.j2 b/xos/synchronizer/templates/bwlimit.sh.j2
index aa4d55e..0fd8b94 100644
--- a/xos/synchronizer/templates/bwlimit.sh.j2
+++ b/xos/synchronizer/templates/bwlimit.sh.j2
@@ -1,22 +1,19 @@
-
-{#
-Copyright 2017-present Open Networking Foundation
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-#}
-
-
 #!/bin/bash
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 #  tc uses the following units when passed as a parameter.
 #  kbps: Kilobytes per second
 #  mbps: Megabytes per second
diff --git a/xos/synchronizer/templates/rc.local.j2 b/xos/synchronizer/templates/rc.local.j2
index 891cf17..4323045 100755
--- a/xos/synchronizer/templates/rc.local.j2
+++ b/xos/synchronizer/templates/rc.local.j2
@@ -1,22 +1,20 @@
-
-{#
-Copyright 2017-present Open Networking Foundation
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-#}
-
-
 #!/bin/sh -e
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
 #
 # rc.local
 #
diff --git a/xos/synchronizer/templates/start-veg-vtn.sh.j2 b/xos/synchronizer/templates/start-veg-vtn.sh.j2
index 47c0f92..2b922bd 100644
--- a/xos/synchronizer/templates/start-veg-vtn.sh.j2
+++ b/xos/synchronizer/templates/start-veg-vtn.sh.j2
@@ -1,23 +1,19 @@
-
-{#
-Copyright 2017-present Open Networking Foundation
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-#}
-
-
 #!/bin/bash
 
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 function mac_to_iface {
     MAC=$1
     ifconfig|grep $MAC| awk '{print $1}'|grep -v '\.'
diff --git a/xos/synchronizer/templates/start-veg.sh.j2 b/xos/synchronizer/templates/start-veg.sh.j2
index 1c4de09..7a0a25f 100755
--- a/xos/synchronizer/templates/start-veg.sh.j2
+++ b/xos/synchronizer/templates/start-veg.sh.j2
@@ -1,23 +1,19 @@
-
-{#
-Copyright 2017-present Open Networking Foundation
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-#}
-
-
 #!/bin/bash
 
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 function mac_to_iface {
     MAC=$1
     ifconfig|grep $MAC| awk '{print $1}'|grep -v '\.'
diff --git a/xos/synchronizer/veg_from_api_config b/xos/synchronizer/veg_from_api_config
deleted file mode 100644
index 04e4743..0000000
--- a/xos/synchronizer/veg_from_api_config
+++ /dev/null
@@ -1,20 +0,0 @@
-# Sets options for the synchronizer
-[observer]
-name=veg
-dependency_graph=/opt/xos/synchronizers/veg/model-deps
-steps_dir=/opt/xos/synchronizers/veg/steps
-sys_dir=/opt/xos/synchronizers/veg/sys
-#logfile=/var/log/xos_backend.log
-log_file=console
-log_level=debug
-pretend=False
-backoff_disabled=True
-save_ansible_output=True
-proxy_ssh=True
-proxy_ssh_key=/opt/cord_profile/node_key
-proxy_ssh_user=root
-accessor_kind=api
-accessor_password=@/opt/xos/services/veg/credentials/xosadmin@opencord.org
-
-[networking]
-use_vtn=True
\ No newline at end of file
diff --git a/xos/synchronizer/run-vtn.sh b/xos/synchronizer/wrappers/__init__.py
old mode 100755
new mode 100644
similarity index 65%
rename from xos/synchronizer/run-vtn.sh
rename to xos/synchronizer/wrappers/__init__.py
index 88b00c9..9881c92
--- a/xos/synchronizer/run-vtn.sh
+++ b/xos/synchronizer/wrappers/__init__.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,12 +12,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-#if [[ ! -e ./veg-observer.py ]]; then
-#    ln -s ../../xos-observer.py veg-observer.py
-#fi
-
-export XOS_DIR=/opt/xos
-cp /root/setup/node_key $XOS_DIR/synchronizers/veg/node_key
-chmod 0600 $XOS_DIR/synchronizers/veg/node_key
-python veg-synchronizer.py  -C $XOS_DIR/synchronizers/veg/vtn_veg_synchronizer_config
diff --git a/xos/synchronizer/wrappers/veeserviceinstance.py b/xos/synchronizer/wrappers/veeserviceinstance.py
new file mode 100644
index 0000000..c82a0c8
--- /dev/null
+++ b/xos/synchronizer/wrappers/veeserviceinstance.py
@@ -0,0 +1,49 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+
+class ORMWrapperVEEServiceInstance(ORMWrapper):
+    @property
+    def veg(self):
+        links = self.stub.ServiceInstanceLink.objects.filter(subscriber_service_instance_id = self.id)
+        for link in links:
+            # cast from ServiceInstance to VSGTenant
+            vegs = self.stub.VEGTenant.objects.filter(id = link.provider_service_instance.id)
+            if vegs:
+                return vegs[0]
+        return None
+
+    # DEPRECATED
+    @property
+    def vcpe(self):
+        return self.veg
+
+    # DEPRECATED
+    @property
+    def vsg(Self):
+        return self.veg
+
+    @property
+    def subscriber(self):
+        links = self.stub.ServiceInstanceLink.objects.filter(provider_service_instance_id = self.id)
+        for link in links:
+            # assume the only thing that links to a VEE must be a subscriber
+            if link.subscriber_service_instance:
+                return link.subscriber_service_instance.leaf_model
+        return None
+
+register_convenience_wrapper("VEEServiceInstance", ORMWrapperVEEServiceInstance)
diff --git a/xos/synchronizer/wrappers/vegtenant.py b/xos/synchronizer/wrappers/vegtenant.py
new file mode 100644
index 0000000..54d1806
--- /dev/null
+++ b/xos/synchronizer/wrappers/vegtenant.py
@@ -0,0 +1,108 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+
+class ORMWrapperVEGTenant(ORMWrapper):
+    sync_attributes = ("wan_container_ip", "wan_container_mac", "wan_container_netbits",
+                       "wan_container_gateway_ip", "wan_container_gateway_mac",
+                       "wan_vm_ip", "wan_vm_mac")
+
+    @property
+    def vee(self):
+        links = self.provided_links.all()
+        for link in links:
+            subscriber_service_instance = link.subscriber_service_instance.leaf_model
+            # Look for something that has an s_tag attribute
+            if (hasattr(subscriber_service_instance, "s_tag")):
+                return subscriber_service_instance
+        return None
+
+    # DEPRECATED
+    @property
+    def volt(self):
+        return self.vee
+    
+    def is_address_manager_service_instance(self, si):
+        # TODO: hardcoded dependency
+        # TODO: VRouterTenant is deprecated
+        return si.leaf_model_name in ["AddressManagerServiceInstance", "VRouterTenant"]
+
+    # DEPRECATED
+    @property
+    def vrouter(self):
+        return self.address_service_instance
+
+    @property
+    def address_service_instance(self):
+        links = self.subscribed_links.all()
+        for link in links:
+            if not self.is_address_manager_service_instance(link.provider_service_instance):
+                continue
+            # cast from ServiceInstance to AddressManagerServiceInstance or similar
+            return link.provider_service_instance.leaf_model
+        return None
+
+    def get_address_service_instance_field(self, name, default=None):
+        if self.address_service_instance:
+            return getattr(self.address_service_instance, name, default)
+        else:
+            return default
+
+    @property
+    def wan_container_ip(self):
+        return self.get_address_service_instance_field("public_ip", None)
+
+    @property
+    def wan_container_mac(self):
+        return self.get_address_service_instance_field("public_mac", None)
+
+    @property
+    def wan_container_netbits(self):
+        return self.get_address_service_instance_field("netbits", None)
+
+    @property
+    def wan_container_gateway_ip(self):
+        return self.get_address_service_instance_field("gateway_ip", None)
+
+    @property
+    def wan_container_gateway_mac(self):
+        return self.get_address_service_instance_field("gateway_mac", None)
+
+    @property
+    def wan_vm_ip(self):
+        tags = self.stub.Tag.objects.filter(name="vm_vrouter_tenant", object_id=self.instance.id, content_type=self.instance.self_content_type_id)
+        if tags:
+            service_instances = self.stub.ServiceInstance.objects.filter(id=int(tags[0].value))
+            if not service_instances:
+                raise Exception("ServiceInstance %d linked to vsg %s does not exist" % (int(tags[0].value), self))
+            return service_instances[0].leaf_model.public_ip
+        else:
+            raise Exception("no vm_vrouter_tenant tag for instance %s" % self.instance)
+
+    @property
+    def wan_vm_mac(self):
+        tags = self.stub.Tag.objects.filter(name="vm_vrouter_tenant", object_id=self.instance.id, content_type=self.instance.self_content_type_id)
+        if tags:
+            service_instances = self.stub.ServiceInstance.objects.filter(id=int(tags[0].value))
+            if not service_instances:
+                raise Exception("ServiceInstance %d linked to vsg %s does not exist" % (int(tags[0].value), self))
+            return service_instances[0].leaf_model.public_mac
+        else:
+            raise Exception("no vm_vrouter_tenant tag for instance %s" % self.instance)
+
+
+register_convenience_wrapper("VEGTenant", ORMWrapperVEGTenant)
diff --git a/xos/synchronizer/run-vtn.sh b/xos/tosca/resources/vegtenant.py
old mode 100755
new mode 100644
similarity index 65%
copy from xos/synchronizer/run-vtn.sh
copy to xos/tosca/resources/vegtenant.py
index 88b00c9..15d80cf
--- a/xos/synchronizer/run-vtn.sh
+++ b/xos/tosca/resources/vegtenant.py
@@ -14,11 +14,9 @@
 # limitations under the License.
 
 
-#if [[ ! -e ./veg-observer.py ]]; then
-#    ln -s ../../xos-observer.py veg-observer.py
-#fi
+from services.veg.models import VEGTenant
+from serviceinstance import XOSServiceInstance
 
-export XOS_DIR=/opt/xos
-cp /root/setup/node_key $XOS_DIR/synchronizers/veg/node_key
-chmod 0600 $XOS_DIR/synchronizers/veg/node_key
-python veg-synchronizer.py  -C $XOS_DIR/synchronizers/veg/vtn_veg_synchronizer_config
+class XOSVegTenant(XOSServiceInstance):
+    provides = "tosca.nodes.VEGTenant"
+    xos_model = VEGTenant
diff --git a/xos/veg-onboard.yaml b/xos/veg-onboard.yaml
index ef43d66..378c453 100644
--- a/xos/veg-onboard.yaml
+++ b/xos/veg-onboard.yaml
@@ -33,7 +33,7 @@
           admin: admin.py
           admin_template: templates/vegadmin.html
           tosca_custom_types: veg.yaml
-          tosca_resource: tosca/resources/vegservice.py
+          tosca_resource: tosca/resources/vegservice.py, tosca/resources/vegtenant.py
           private_key: file:///opt/xos/key_import/veg_rsa
           public_key: file:///opt/xos/key_import/veg_rsa.pub
 
diff --git a/xos/veg.m4 b/xos/veg.m4
index 8e2a289..309db0b 100644
--- a/xos/veg.m4
+++ b/xos/veg.m4
@@ -49,4 +49,11 @@
             docker_insecure_registry:
                 type: boolean
                 required: false
-                description: If true, then the hostname:port specified in docker_image_name will be treated as an insecure registry
\ No newline at end of file
+                description: If true, then the hostname:port specified in docker_image_name will be treated as an insecure registry
+
+    tosca.nodes.VEGTenant:
+        derived_from: tosca.nodes.Root
+        description: >
+            A VEG Tenant.
+        properties:
+            xos_base_tenant_props
diff --git a/xos/veg.yaml b/xos/veg.yaml
index 913fb17..659135e 100644
--- a/xos/veg.yaml
+++ b/xos/veg.yaml
@@ -19,6 +19,7 @@
 # compile this with "m4 veg.m4 > veg.yaml"
 
 # include macros
+
 # Note: Tosca derived_from isn't working the way I think it should, it's not
 #    inheriting from the parent template. Until we get that figured out, use
 #    m4 macros do our inheritance
@@ -112,4 +113,18 @@
             docker_insecure_registry:
                 type: boolean
                 required: false
-                description: If true, then the hostname:port specified in docker_image_name will be treated as an insecure registry
\ No newline at end of file
+                description: If true, then the hostname:port specified in docker_image_name will be treated as an insecure registry
+
+    tosca.nodes.VEGTenant:
+        derived_from: tosca.nodes.Root
+        description: >
+            A VEG Tenant.
+        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