Fix issue with saving tenants
diff --git a/containers/synchronizer/Makefile b/containers/synchronizer/Makefile
index 733e7b8..352616a 100644
--- a/containers/synchronizer/Makefile
+++ b/containers/synchronizer/Makefile
@@ -1,6 +1,6 @@
 IMAGE_NAME:=xosproject/xos-synchronizer-openstack
 CONTAINER_NAME:=xos-synchronizer
-NO_DOCKER_CACHE?=true
+NO_DOCKER_CACHE?=false
 
 .PHONY: build
 build: ; sudo docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
diff --git a/xos/services/vpn/admin.py b/xos/services/vpn/admin.py
index 38737fa..bd33d99 100644
--- a/xos/services/vpn/admin.py
+++ b/xos/services/vpn/admin.py
@@ -20,7 +20,8 @@
         super(VPNServiceForm, self).__init__(*args, **kwargs)
 
         if self.instance:
-            self.fields['exposed_ports'].initial = self.instance.exposed_ports
+            self.fields['exposed_ports'].initial = (
+                self.instance.exposed_ports_str)
 
     def save(self, commit=True):
         self.instance.exposed_ports = self.cleaned_data['exposed_ports']
@@ -28,6 +29,7 @@
 
     def clean_exposed_ports(self):
         exposed_ports = self.cleaned_data['exposed_ports']
+        self.instance.exposed_ports_str = exposed_ports
         port_mapping = {"udp": [], "tcp": []}
         parts = exposed_ports.split(",")
         for part in parts:
@@ -37,7 +39,9 @@
             elif " " in part:
                 (protocol, ports) = part.split(None, 1)
             else:
-                raise XOSValidationError('malformed port specifier %s, format example: "tcp 123, tcp 201:206, udp 333"' % part)
+                raise XOSValidationError(
+                    'malformed port specifier %s, format example: ' +
+                    '"tcp 123, tcp 201:206, udp 333"' % part)
 
             protocol = protocol.strip()
             ports = ports.strip()
@@ -46,9 +50,11 @@
                 raise XOSValidationError('unknown protocol %s' % protocol)
 
             if "-" in ports:
-                port_mapping[protocol].extend(self.parse_port_range(ports, "-"))
+                port_mapping[protocol].extend(
+                    self.parse_port_range(ports, "-"))
             elif ":" in ports:
-                port_mapping[protocol].extend(self.parse_port_range(ports, ":"))
+                port_mapping[protocol].extend(
+                    self.parse_port_range(ports, ":"))
             else:
                 port_mapping[protocol].append(int(ports))
 
@@ -75,7 +81,8 @@
     list_display_links = ('backend_status_icon', 'name', )
 
     fieldsets = [(None, {'fields': ['backend_status_text', 'name', 'enabled',
-                                    'versionNumber', 'description', "view_url", 'exposed_ports'],
+                                    'versionNumber', 'description', "view_url",
+                                    'exposed_ports'],
                          'classes':['suit-tab suit-tab-general']})]
 
     readonly_fields = ('backend_status_text', )
@@ -102,11 +109,16 @@
     """The form used to create and edit a VPNTenant.
 
     Attributes:
-        creator (forms.ModelChoiceField): The XOS user that created this tenant.
-        client_conf (forms.CharField): The readonly configuration used on the client to connect to this Tenant.
-        server_address (forms.GenericIPAddressField): The ip address on the VPN of this Tenant.
-        client_address (forms.GenericIPAddressField): The ip address on the VPN of the client.
-        is_persistent (forms.BooleanField): Determines if this Tenant keeps this connection alive through failures.
+        creator (forms.ModelChoiceField): The XOS user that created this
+            tenant.
+        client_conf (forms.CharField): The readonly configuration used on the
+            client to connect to this Tenant.
+        server_address (forms.GenericIPAddressField): The ip address on the VPN
+            of this Tenant.
+        client_address (forms.GenericIPAddressField): The ip address on the VPN
+            of the client.
+        is_persistent (forms.BooleanField): Determines if this Tenant keeps
+            this connection alive through failures.
     """
     creator = forms.ModelChoiceField(queryset=User.objects.all())
     server_network = forms.GenericIPAddressField(
@@ -114,15 +126,18 @@
     vpn_subnet = forms.GenericIPAddressField(protocol="IPv4", required=True)
     is_persistent = forms.BooleanField(required=False)
     clients_can_see_each_other = forms.BooleanField(required=False)
-    failover_servers = forms.ModelMultipleChoiceField(queryset=VPNTenant.get_tenant_objects(), required=False)
-    protocol = forms.ChoiceField(required=True, choices=[("udp", "udp"), ("tcp", "tcp")])
+    failover_servers = forms.ModelMultipleChoiceField(
+        queryset=VPNTenant.get_tenant_objects(), required=False)
+    protocol = forms.ChoiceField(required=True, choices=[
+        ("udp", "udp"), ("tcp", "tcp")])
 
     def __init__(self, *args, **kwargs):
         super(VPNTenantForm, self).__init__(*args, **kwargs)
         self.fields['kind'].widget.attrs['readonly'] = True
         # self.fields['script_name'].widget.attrs['readonly'] = True
         self.fields[
-            'provider_service'].queryset = VPNService.get_service_objects().all()
+            'provider_service'].queryset = (
+                VPNService.get_service_objects().all())
 
         self.fields['kind'].initial = VPN_KIND
 
@@ -132,11 +147,15 @@
             self.fields[
                 'server_network'].initial = self.instance.server_network
             self.fields[
-                'clients_can_see_each_other'].initial = self.instance.clients_can_see_each_other
+                'clients_can_see_each_other'].initial = (
+                    self.instance.clients_can_see_each_other)
             self.fields['is_persistent'].initial = self.instance.is_persistent
             self.initial['protocol'] = self.instance.protocol
             if (self.instance.failover_servers):
-                self.initial['failover_servers'] = [model.pk for model in list(serializers.deserialize('json', self.instance.failover_servers))]
+                self.initial['failover_servers'] = [
+                    model.pk for model in list(
+                        serializers.deserialize(
+                            'json', self.instance.failover_servers))]
 
         if (not self.instance) or (not self.instance.pk):
             self.fields['creator'].initial = get_request().user
@@ -145,8 +164,8 @@
             self.fields['clients_can_see_each_other'].initial = True
             self.fields['is_persistent'].initial = True
             if VPNService.get_service_objects().exists():
-                self.fields["provider_service"].initial = VPNService.get_service_objects().all()[
-                    0]
+                self.fields["provider_service"].initial = (
+                    VPNService.get_service_objects().all()[0])
 
     def save(self, commit=True):
         self.instance.creator = self.cleaned_data.get("creator")
@@ -155,34 +174,50 @@
         self.instance.server_network = self.cleaned_data.get('server_network')
         self.instance.clients_can_see_each_other = self.cleaned_data.get(
             'clients_can_see_each_other')
-        self.instance.failover_servers = serializers.serialize("json", self.cleaned_data.get('failover_servers'))
+        self.instance.failover_servers = (
+            serializers.serialize(
+                "json", self.cleaned_data.get('failover_servers')))
 
         self.instance.protocol = self.cleaned_data.get("protocol")
-        self.instance.port_number = self.instance.provider_service.get_next_available_port(self.instance.protocol)
-
-        if (not self.instance.ca_crt):
-            self.instance.ca_crt = self.generate_ca_crt()
+        self.instance.port_number = (
+            self.instance.provider_service.get_next_available_port(
+                self.instance.protocol))
 
         result = super(VPNTenantForm, self).save(commit=commit)
-        pki_dir = "/opt/openvpn/easyrsa3/server-" + self.instance.id
+        pki_dir = "/opt/openvpn/easyrsa3/server-" + result.id
         if (not os.path.isdir(pki_dir)):
             os.makedirs(pki_dir)
             shutil.copy2("/opt/openvpn/easyrsa3/openssl-1.0.cnf", pki_dir)
             shutil.copy2("/opt/openvpn/easyrsa3/easyrsa", pki_dir)
-            shutil.copytree("/opt/openvpn/easyrsa3/x509-types", pki_dir + "/x509-types")
-            (stdout, stderr) = Popen(pki_dir + "/easyrsa --batch init-pki nopass", shell=True, stdout=PIPE, stderr=PIPE).communicate()
+            shutil.copytree("/opt/openvpn/easyrsa3/x509-types",
+                            pki_dir + "/x509-types")
+            (stdout, stderr) = Popen(
+                pki_dir + "/easyrsa --batch init-pki nopass",
+                shell=True,
+                stdout=PIPE,
+                stderr=PIPE).communicate()
             if (stderr):
-                raise XOSConfigurationError("init-pki failed with standard out:" + str(stdout) + " and stderr: " + str(stderr))
-            (stdout, stderr) = Popen(pki_dir + "/easyrsa --batch --req-cn=XOS build-ca nopass", shell=True, stdout=PIPE, stderr=PIPE).communicate()
+                raise XOSConfigurationError(
+                    "init-pki failed with standard out:" + str(stdout) +
+                    " and stderr: " + str(stderr))
+            (stdout, stderr) = Popen(
+                pki_dir + "/easyrsa --batch --req-cn=XOS build-ca nopass",
+                shell=True,
+                stdout=PIPE,
+                stderr=PIPE).communicate()
             if (stderr):
-                raise XOSConfigurationError("build-ca failed with standard out:" + str(stdout) + " and stderr: " + str(stderr))
-
-            self.instance.ca_crt = self.generate_ca_crt(self.instance.id)
+                raise XOSConfigurationError(
+                    "build-ca failed with standard out:" + str(stdout) +
+                    " and stderr: " + str(stderr))
+            self.instance.ca_crt = self.generate_ca_crt(result.id)
+            return super(VPNTenantForm, self).save(commit=commit)
         return result
 
     def generate_ca_crt(self, server_id):
         """str: Generates the ca cert by reading from the ca file"""
-        with open("/opt/openvpn/easyrsa3/server-" + server_id + "/pki/ca.crt") as crt:
+        with open(
+                "/opt/openvpn/easyrsa3/server-" + server_id + "/pki/ca.crt"
+                ) as crt:
             return crt.readlines()
 
     class Meta:
@@ -197,30 +232,43 @@
                           'instance', 'server_network', 'vpn_subnet')
     fieldsets = [(None, {'fields': ['backend_status_text', 'kind',
                                     'provider_service', 'instance', 'creator',
-                                    'server_network', 'vpn_subnet', 'is_persistent',
-                                    'clients_can_see_each_other', 'failover_servers', "protocol"],
+                                    'server_network', 'vpn_subnet',
+                                    'is_persistent',
+                                    'clients_can_see_each_other',
+                                    'failover_servers', "protocol"],
                          'classes': ['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', 'instance')
     form = VPNTenantForm
     inlines = [TenantPrivilegeInline]
 
-    suit_form_tabs = (('general', 'Details'), ('tenantprivileges', 'Privileges'))
+    suit_form_tabs = (('general', 'Details'),
+                      ('tenantprivileges', 'Privileges'))
 
     def queryset(self, request):
         return VPNTenant.get_tenant_objects_by_user(request.user)
 
     def certificate_name(self, tenant_privilege):
-        return str(tenant_privilege.user.email) + "-" + str(tenant_privilege.tenant.id)
+        return (str(tenant_privilege.user.email) +
+                "-" + str(tenant_privilege.tenant.id))
 
     def save_formset(self, request, form, formset, change):
-        super(VPNTenantAdmin, self).save_formset(request, form, formset, change)
+        super(VPNTenantAdmin, self).save_formset(
+            request, form, formset, change)
         for obj in formset.deleted_objects:
-            # If anything deleated was a TenantPrivilege then revoke the certificate
+            # If anything deleated was a TenantPrivilege then revoke the
+            # certificate
             if type(obj) is TenantPrivilege:
                 certificate = self.certificate_name(obj)
-                (stdout, stderr) = Popen("/opt/openvpn/easyrsa3/server-" + obj.tenant.id + "/easyrsa --batch revoke " + certificate, shell=True, stdout=PIPE, stderr=PIPE).communicate()
+                (stdout, stderr) = Popen(
+                    "/opt/openvpn/easyrsa3/server-" + obj.tenant.id +
+                    "/easyrsa --batch revoke " + certificate,
+                    shell=True,
+                    stdout=PIPE,
+                    stderr=PIPE).communicate()
                 if (stderr):
-                    raise XOSConfigurationError("revoke failed with standard out:" + str(stdout) + " and stderr: " + str(stderr))
+                    raise XOSConfigurationError(
+                        "revoke failed with standard out:" + str(stdout) +
+                        " and stderr: " + str(stderr))
             # TODO(jermowery): determine if this is necessary.
             # if type(obj) is VPNTenant:
                 # if the tenant was deleted revoke all certs assoicated
@@ -230,9 +278,17 @@
             # If there were any new TenantPrivlege objects then create certs
             if type(obj) is TenantPrivilege:
                 certificate = self.certificate_name(obj)
-                (stdout, stderr) = Popen("/opt/openvpn/easyrsa3/server-" + obj.tenant.id + "/easyrsa --batch build-client-full " + certificate + " nopass", shell=True, stdout=PIPE, stderr=PIPE).communicate()
+                (stdout, stderr) = Popen(
+                    "/opt/openvpn/easyrsa3/server-" + obj.tenant.id +
+                    "/easyrsa --batch build-client-full " + certificate +
+                    " nopass",
+                    shell=True,
+                    stdout=PIPE,
+                    stderr=PIPE).communicate()
                 if (stderr):
-                    raise XOSConfigurationError("build-client-full failed with standard out:" + str(stdout) + " and stderr: " + str(stderr))
+                    raise XOSConfigurationError(
+                        "build-client-full failed with standard out:" +
+                        str(stdout) + " and stderr: " + str(stderr))
 
 # Associate the admin forms with the models.
 admin.site.register(VPNService, VPNServiceAdmin)
diff --git a/xos/services/vpn/models.py b/xos/services/vpn/models.py
index a33f06f..76bc516 100644
--- a/xos/services/vpn/models.py
+++ b/xos/services/vpn/models.py
@@ -15,26 +15,42 @@
         app_label = "vpn"
         verbose_name = "VPN Service"
 
-    default_attributes = {'exposed_ports': None}
+    default_attributes = {'exposed_ports': None,
+                          'exposed_ports_str': None}
 
     @property
     def exposed_ports(self):
         return self.get_attribute("exposed_ports",
-                                    self.default_attributes["exposed_ports"])
+                                  self.default_attributes["exposed_ports"])
 
     @exposed_ports.setter
     def exposed_ports(self, value):
         self.set_attribute("exposed_ports", value)
 
+    @property
+    def exposed_ports_str(self):
+        return self.get_attribute("exposed_ports_str",
+                                  self.default_attributes["exposed_ports_str"])
+
+    @exposed_ports_str.setter
+    def exposed_ports_str(self, value):
+        self.set_attribute("exposed_ports_str", value)
+
     def get_next_available_port(self, protocol):
         if protocol != "udp" and protocol != "tcp":
             raise XOSValidationError("Port protocol must be udp or tcp")
         if not self.exposed_ports[protocol]:
-            raise XOSValidationError("No availble ports for protocol: " + protocol)
-        tenants = [tenant for tenant in VPNTenant.get_tenant_objects().all() if tenant.protocol == protocol]
+            raise XOSValidationError(
+                "No availble ports for protocol: " + protocol)
+        tenants = [
+            tenant for tenant in VPNTenant.get_tenant_objects().all()
+            if tenant.protocol == protocol]
         port_numbers = self.exposed_ports[protocol]
         for port_number in port_numbers:
-            if count([tenant for tenant in tenants if tenant.port_number == port_number]) == 0:
+            if (
+                len([
+                    tenant for tenant in tenants
+                    if tenant.port_number == port_number]) == 0):
                 return port_number
 
 
@@ -76,7 +92,8 @@
 
     @property
     def protocol(self):
-        return self.get_attribute("protocol", self.default_attributes["protocol"])
+        return self.get_attribute(
+            "protocol", self.default_attributes["protocol"])
 
     @protocol.setter
     def protocol(self, value):
@@ -84,7 +101,8 @@
 
     @property
     def addresses(self):
-        """Mapping[str, str]: The ip, mac address, and subnet of the NAT network of this Tenant."""
+        """Mapping[str, str]: The ip, mac address, and subnet of the NAT
+            network of this Tenant."""
         if (not self.id) or (not self.instance):
             return {}
 
@@ -144,7 +162,8 @@
 
     @property
     def failover_servers(self):
-        self.get_attribute("failover_servers", self.default_attributes["failover_servers"])
+        self.get_attribute(
+            "failover_servers", self.default_attributes["failover_servers"])
 
     @failover_servers.setter
     def failover_servers(self, value):
@@ -152,7 +171,8 @@
 
     @property
     def clients_can_see_each_other(self):
-        """bool: True if the client can see the subnet of the server, false otherwise."""
+        """bool: True if the client can see the subnet of the server, false
+            otherwise."""
         return self.get_attribute(
             "clients_can_see_each_other",
             self.default_attributes['clients_can_see_each_other'])
@@ -181,7 +201,8 @@
 
     @property
     def script_text(self):
-        return self.get_attribute("script_text", self.default_attributes['script_text'])
+        return self.get_attribute(
+            "script_text", self.default_attributes['script_text'])
 
     @script_text.setter
     def script_text(self, value):
@@ -212,18 +233,24 @@
         return script
 
     def get_client_cert(self, client_name):
-        return open("/opt/openvpn/easyrsa3/server-" + self.id + "/pki/issued/" + client_name + ".crt").readlines()
+        return open(
+            "/opt/openvpn/easyrsa3/server-" + self.id + "/pki/issued/" +
+            client_name + ".crt").readlines()
 
     def get_client_key(self, client_name):
-        return open("/opt/openvpn/easyrsa3/server-" + self.id + "/pki/private/" + client_name + ".key").readlines()
+        return open(
+            "/opt/openvpn/easyrsa3/server-" + self.id + "/pki/private/" +
+            client_name + ".key").readlines()
 
     def generate_client_conf(self, client_name):
-        """str: Generates the client configuration to use to connect to this VPN server.
+        """str: Generates the client configuration to use to connect to this
+            VPN server.
         """
         conf = ("client\n" +
                 "dev tun\n" +
                 "proto " + self.protocol + "\n" +
-                "remote " + str(self.nat_ip) + " " + str(self.port_number) + "\n" +
+                "remote " + str(self.nat_ip) + " " + str(self.port_number) +
+                "\n" +
                 "resolv-retry infinite\n" +
                 "nobind\n" +
                 "ca ca.crt\n" +