Merge "Fix fallthrough param for coredns to avoid coredns misconfiguration"
diff --git a/.kitchen.yml b/.kitchen.yml
index be4fc3c..3c5f6be 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -72,11 +72,6 @@
       pillars-from-files:
         kubernetes.sls: tests/pillar/pool_cluster_with_domain.sls
 
-  - name: master_contrail
-    provisioner:
-      pillars-from-files:
-        kubernetes.sls: tests/pillar/master_contrail.sls
-
   - name: master_contrail4_0
     provisioner:
       pillars-from-files:
diff --git a/.travis.yml b/.travis.yml
index d8b6989..9397e45 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,18 +17,15 @@
 env:
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19 SUITE=master-cluster
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19 SUITE=pool-cluster
-  - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19 SUITE=master-contrail
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19 SUITE=master-contrail4-0
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19 SUITE=pool-contrail4-0
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2016.3/salt:2018_11_19 SUITE=common-storageclass
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2017.7/salt:2018_11_19 SUITE=master-cluster
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2017.7/salt:2018_11_19 SUITE=pool-cluster
-  - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2017.7/salt:2018_11_19 SUITE=master-contrail
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2017.7/salt:2018_11_19 SUITE=master-contrail4-0
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-2017.7/salt:2018_11_19 SUITE=pool-contrail4-0
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19 SUITE=master-cluster
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19 SUITE=pool-cluster
-  - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19 SUITE=master-contrail
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19 SUITE=master-contrail4-0
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19 SUITE=pool-contrail4-0
   - PLATFORM=docker-dev-local.docker.mirantis.net/epcim/salt/saltstack-ubuntu-xenial-salt-stable/salt:2018_11_19 SUITE=common-storageclass
diff --git a/README.rst b/README.rst
index 7ec845b..77565ef 100644
--- a/README.rst
+++ b/README.rst
@@ -411,12 +411,6 @@
 .. code-block:: yaml
 
     kubernetes:
-      common:
-        addons:
-          contrail_network_controller:
-            enabled: true
-            namespace: kube-system
-            image: yashulyak/contrail-controller:latest
       master:
         network:
           opencontrail:
diff --git a/kubernetes/_common.sls b/kubernetes/_common.sls
index 5d304df..88a9fcd 100644
--- a/kubernetes/_common.sls
+++ b/kubernetes/_common.sls
@@ -5,29 +5,15 @@
   - names: {{ common.pkgs }}
 
 {%- if common.hyperkube is defined %}
-/tmp/hyperkube:
-  file.directory:
-    - user: root
-    - group: root
-
-hyperkube-copy:
-  cmd.run:
-    - name: docker run --rm -v /tmp/hyperkube:/tmp/hyperkube --entrypoint cp {{ common.hyperkube.image }} -vr /hyperkube /tmp/hyperkube
-    - require:
-      - file: /tmp/hyperkube
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
 
 /usr/bin/hyperkube:
   file.managed:
-    - source: /tmp/hyperkube/hyperkube
+    - source: {{ common.hyperkube.source }}
     - mode: 751
     - makedirs: true
     - user: root
     - group: root
-    - require:
-      - cmd: hyperkube-copy
+    - source_hash: {{ common.hyperkube.source_hash }}
     {%- if grains.get('noservices') %}
     - onlyif: /bin/false
     {%- endif %}
@@ -180,10 +166,12 @@
 /etc/criproxy:
   file.absent
 
+{%- if not common.get('containerd', {}).get('enabled') %}
 dockershim_service:
   service.dead:
   - name: dockershim
   - enable: False
+{%- endif %}
 
 criproxy_service:
   service.dead:
@@ -204,7 +192,7 @@
   file.absent
 
 {%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
-/etc/kubernetes/cloud-config.conf:
+/etc/kubernetes/cloud-config:
   file.managed:
   - source: salt://kubernetes/files/cloudprovider/cloud-config-openstack.conf
   - template: jinja
@@ -271,6 +259,11 @@
     - file: /usr/bin/hyperkube
     - file: /etc/kubernetes/kubelet.kubeconfig
     - file: manifest_dir_create
+    {%- if common.get('cloudprovider', {}).get('enabled') %}
+    {%- if common.get('cloudprovider', {}).get('provider') == 'openstack' and not pillar.get('kubernetes', {}).get('master', false) %}
+    - file: /etc/kubernetes/cloud-config
+    {%- endif %}
+    {%- endif %}
   {%- if grains.get('noservices') %}
   - onlyif: /bin/false
   {%- endif %}
diff --git a/kubernetes/files/calico/calico-node.service.ctr b/kubernetes/files/calico/calico-node.service.ctr
new file mode 100644
index 0000000..30ec001
--- /dev/null
+++ b/kubernetes/files/calico/calico-node.service.ctr
@@ -0,0 +1,47 @@
+[Unit]
+Description=calico-node
+After=containerd.service
+Requires=containerd.service
+
+[Service]
+ExecStartPre=-/usr/bin/ctr t delete -f calico-node
+ExecStartPre=-/usr/bin/ctr c rm calico-node
+ExecStartPre=/usr/bin/ctr i pull {{ calico.image }}
+ExecStartPre=/bin/mkdir -p /var/run/calico
+ExecStart=/usr/bin/ctr run --net-host --privileged \
+ --env NODENAME={{ hostname }} \
+ --env IP={{ address }} \
+{%- if calico.network_backend is defined %}
+ --env CALICO_NETWORKING_BACKEND="{{ calico.network_backend }}" \
+{%- endif %}
+ --env AS={{ calico.get('as', '64512') }} \
+ --env NO_DEFAULT_POOLS={{ calico.get('no_default_pools', false ) }} \
+ --env CALICO_STARTUP_LOGLEVEL={{ calico.get('log_level', 'INFO') }} \
+ --env CLUSTER_TYPE='k8s,bgp' \
+ --env CALICO_LIBNETWORK_ENABLED={{ calico.get('libnetwork_enabled', false ) }} \
+ --env ETCD_ENDPOINTS={% for member in calico.etcd.members %}http{% if calico.etcd.get('ssl', {}).get('enabled') %}s{% endif %}://{{ member.host }}:{{ member.port }}{% if not loop.last %},{% endif %}{% endfor %} \
+{%- if calico.etcd.get('ssl', {}).get('enabled') %}
+ --env ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem \
+ --env ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt \
+ --env ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key \
+ --mount type=bind,src=/var/lib/etcd/,dst=/var/lib/etcd/,options=rbind:ro \
+{%- endif %}
+{%- if calico.get('prometheus', {}).get('enabled') %}
+ --env FELIX_PROMETHEUSMETRICSENABLED=true \
+ --env FELIX_PROMETHEUSMETRICSPORT={{ calico.prometheus.get('port', 9091) }} \
+{%- endif %}
+ --mount type=bind,src=/var/log/calico,dst=/var/log/calico,options=rbind:rw \
+ --mount type=bind,src=/var/lib/calico,dst=/var/lib/calico,options=rbind:rw \
+ --mount type=bind,src=/lib/modules,dst=/lib/modules,options=rbind:rw \
+ --mount type=bind,src=/var/run/calico,dst=/var/run/calico,options=rbind:rw \
+ {{ calico.image }} \
+ calico-node
+
+Restart=always
+RestartSec=10s
+
+ExecStop=-/usr/bin/ctr t delete -f calico-node
+ExecStop=-/usr/bin/ctr c rm calico-node
+
+[Install]
+WantedBy=multi-user.target
diff --git a/kubernetes/files/cloudprovider/cloud-config-openstack.conf b/kubernetes/files/cloudprovider/cloud-config-openstack.conf
index 48d09e6..92a1cdd 100644
--- a/kubernetes/files/cloudprovider/cloud-config-openstack.conf
+++ b/kubernetes/files/cloudprovider/cloud-config-openstack.conf
@@ -3,13 +3,16 @@
 auth-url={{ common.cloudprovider.params.auth_url }}
 username={{ common.cloudprovider.params.username }}
 password={{ common.cloudprovider.params.password }}
-region={{ common.cloudprovider.params.region }}
-{%- if common.cloudprovider.params.tenant_id is defined %}
 tenant-id={{ common.cloudprovider.params.tenant_id }}
+{%- if common.cloudprovider.params.region is defined %}
+region={{ common.cloudprovider.params.region }}
 {%- endif %}
 {%- if common.cloudprovider.params.tenant_name is defined %}
 tenant-name={{ common.cloudprovider.params.tenant_name }}
 {%- endif %}
+{%- if common.cloudprovider.params.trust_id is defined %}
+trust-id={{ common.cloudprovider.params.trust_id }}
+{%- endif %}
 {%- if common.cloudprovider.params.domain_id is defined %}
 domain-id={{ common.cloudprovider.params.domain_id }}
 {%- endif %}
@@ -19,8 +22,27 @@
 
 
 [LoadBalancer]
+{%- if common.cloudprovider.params.subnet_id is defined %}
+use-octavia=true
 subnet-id={{ common.cloudprovider.params.subnet_id }}
+{%- endif %}
+{%- if common.cloudprovider.params.lb_method is defined %}
+lb-method={{ common.cloudprovider.params.lb_method }}
+{%- endif %}
 {%- if common.cloudprovider.params.floating_network_id is defined %}
 floating-network-id={{ common.cloudprovider.params.floating_network_id }}
 {%- endif %}
-lb-version=v2
+{%- if common.cloudprovider.params.create_monitor is defined %}
+create-monitor={{ common.cloudprovider.params.create_monitor }}
+monitor-delay={{ common.cloudprovider.params.monitor_delay }}
+monitor-timeout={{ common.cloudprovider.params.monitor_timeout }}
+monitor-max-retries={{ common.cloudprovider.params.monitor_max_retries }}
+{%- endif %}
+{%- if common.cloudprovider.params.manage_security_groups is defined %}
+manage-security-groups={{ common.cloudprovider.params.manage_security_groups }}
+node-security-group={{ common.cloudprovider.params.node_security_group }}
+{%- endif %}
+
+
+[BlockStorage]
+ignore-volume-az=true
diff --git a/kubernetes/files/conformance/conformance.yml b/kubernetes/files/conformance/conformance.yml
new file mode 100644
index 0000000..3e6a424
--- /dev/null
+++ b/kubernetes/files/conformance/conformance.yml
@@ -0,0 +1,86 @@
+{%- from "kubernetes/map.jinja" import full_version -%}
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: conformance
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: conformance
+  namespace: conformance
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRoleBinding
+metadata:
+  name: conformance
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: conformance
+subjects:
+- kind: ServiceAccount
+  name: conformance
+  namespace: conformance
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRole
+metadata:
+  name: conformance
+  namespace: conformance
+rules:
+- apiGroups:
+  - '*'
+  resources:
+  - '*'
+  verbs:
+  - '*'
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: conformance
+  namespace: conformance
+spec:
+  hostNetwork: true
+  restartPolicy: Never
+  serviceAccountName: conformance
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: node-role.kubernetes.io/master
+            operator: In
+            values:
+            - "true"
+  tolerations:
+  - key: node-role.kubernetes.io/master
+    effect: NoSchedule
+  containers:
+  - command:
+    - /bin/bash
+    - -c
+    - set -o pipefail && /entrypoint.sh | tee /report/conformance.log
+    name: conformance
+    imagePullPolicy: Always
+    securityContext:
+      privileged: true
+    env:
+    - name: API_SERVER
+      value: http://localhost:8080
+# Uncomment to use FOCUS for conformance runs
+#    - name: FOCUS
+#      value: "Conformance"
+#    - name: CONCURRENCY
+#      value: 25
+    image: docker-prod-local.artifactory.mirantis.com/mirantis/kubernetes/k8s-conformance:v{{ full_version }}
+    volumeMounts:
+    - mountPath: /report
+      name: output-volume
+      mountPropagation: Bidirectional
+  volumes:
+  - hostPath:
+      path: /tmp/conformance
+    name: output-volume
\ No newline at end of file
diff --git a/kubernetes/files/conformance/virtlet_conformance.yml b/kubernetes/files/conformance/virtlet_conformance.yml
new file mode 100644
index 0000000..6e928e1
--- /dev/null
+++ b/kubernetes/files/conformance/virtlet_conformance.yml
@@ -0,0 +1,81 @@
+{%- from "kubernetes/map.jinja" import common -%}
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: virtlet-conformance
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: virtlet-conformance
+  namespace: virtlet-conformance
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRoleBinding
+metadata:
+  name: virtlet-conformance
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: virtlet-conformance
+subjects:
+- kind: ServiceAccount
+  name: virtlet-conformance
+  namespace: virtlet-conformance
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRole
+metadata:
+  name: virtlet-conformance
+  namespace: virtlet-conformance
+rules:
+- apiGroups:
+  - '*'
+  resources:
+  - '*'
+  verbs:
+  - '*'
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: virtlet-conformance
+  namespace: virtlet-conformance
+spec:
+  hostNetwork: true
+  restartPolicy: Never
+  serviceAccountName: virtlet-conformance
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: node-role.kubernetes.io/master
+            operator: In
+            values:
+            - "true"
+  tolerations:
+  - key: node-role.kubernetes.io/master
+    effect: NoSchedule
+  containers:
+  - command:
+    - /bin/bash
+    - -c
+    - set -o pipefail && /virtlet-e2e-tests -include-cloud-init-tests -junitOutput /report/report.xml
+      -image cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img
+      -sshuser ubuntu -memoryLimit 1024 -alsologtostderr -cluster-url http://127.0.0.1:8080
+      -ginkgo.focus '\[Conformance\]' | tee /report/virtlet_conformance.log
+    name: virtlet-conformance
+    imagePullPolicy: Always
+    securityContext:
+      privileged: true
+    image: {{ common.addons.virtlet.image }}
+    volumeMounts:
+    - mountPath: /report
+      name: output-volume
+      mountPropagation: Bidirectional
+  volumes:
+  - hostPath:
+      path: /tmp/virtlet-conformance
+    name: output-volume
\ No newline at end of file
diff --git a/kubernetes/files/dockershim/default.master b/kubernetes/files/dockershim/default.master
index f224475..6ce580e 100644
--- a/kubernetes/files/dockershim/default.master
+++ b/kubernetes/files/dockershim/default.master
@@ -11,7 +11,11 @@
 --cluster_dns={{ common.addons.dns.server }} \
 --cluster_domain={{ common.addons.dns.domain|replace('_', '-') }} \
 --cni-bin-dir={{ master.apiserver.get('cni_bin_dir', '/opt/cni/bin') }} \
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+--hostname-override={{ master.host.name }}.{{ common.cluster_domain }} \
+{%- else %}
 --hostname-override={{ master.host.name }} \
+{%- endif %}
 --v={{ master.get('verbosity', 2) }} \
 --node-labels=node-role.kubernetes.io/master=true \
 {%- if common.hyperkube.pause_image is defined %}
@@ -21,10 +25,7 @@
 --register-with-taints=node-role.kubernetes.io/master=:NoSchedule \
 {%- endif %}
 {%- if common.get('cloudprovider', {}).get('enabled') %}
---cloud-provider={{ common.cloudprovider.provider }} \
-{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
---cloud-config=/etc/kubernetes/cloud-config.conf \
-{%- endif %}
+--cloud-provider=external \
 {%- endif %}
 {%- for key, value in master.get('kubelet', {}).get('daemon_opts', {}).items() %}
 --{{ key }}={{ value }} \
diff --git a/kubernetes/files/dockershim/default.pool b/kubernetes/files/dockershim/default.pool
index 1cbbbd7..5c47d35 100644
--- a/kubernetes/files/dockershim/default.pool
+++ b/kubernetes/files/dockershim/default.pool
@@ -11,7 +11,11 @@
 --allow-privileged={{ pool.kubelet.allow_privileged }} \
 --cluster_dns={{ common.addons.dns.server }} \
 --cluster_domain={{ common.addons.dns.domain|replace('_', '-') }} \
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+--hostname-override={{ pool.host.name }}.{{ common.cluster_domain }} \
+{%- else %}
 --hostname-override={{ pool.host.name }} \
+{%- endif %}
 --v={{ pool.get('verbosity', 2) }} \
 {%- if common.hyperkube.pause_image is defined %}
 --pod-infra-container-image={{ common.hyperkube.pause_image }} \
@@ -28,9 +32,9 @@
 --cni-bin-dir={{ pool.apiserver.get('cni_bin_dir', '/opt/cni/bin') }} \
 --file-check-frequency={{ pool.kubelet.frequency }} \
 {%- if common.get('cloudprovider', {}).get('enabled') %}
---cloud-provider={{ common.cloudprovider.provider }} \
+--cloud-provider=external \
 {%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
---cloud-config=/etc/kubernetes/cloud-config.conf \
+--cloud-config=/etc/kubernetes/cloud-config \
 {%- endif %}
 {%- endif %}
 --enable-controller-attach-detach={{ pool.get('enable-controller-attach-detach', 'false') }} \
diff --git a/kubernetes/files/kube-addon-manager/kube-addons.sh b/kubernetes/files/kube-addon-manager/kube-addons.sh
index 8aefacc..e09ab22 100644
--- a/kubernetes/files/kube-addon-manager/kube-addons.sh
+++ b/kubernetes/files/kube-addon-manager/kube-addons.sh
@@ -53,6 +53,7 @@
   extensions/v1beta1/ReplicaSet
   apps/v1beta1/StatefulSet
   apps/v1beta1/Deployment
+  storage.k8s.io/v1/StorageClass
 )
 
 ADDON_CHECK_INTERVAL_SEC=${TEST_ADDON_CHECK_INTERVAL_SEC:-60}
diff --git a/kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-configmap.yml b/kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-configmap.yml
deleted file mode 100644
index 85bb373..0000000
--- a/kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-configmap.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-{%- from "kubernetes/map.jinja" import common with context -%}
-{%- from "kubernetes/map.jinja" import master with context -%}
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: contrail-kube-manager
-  namespace: {{ common.addons.contrail_network_controller.get('namespace', 'kube-system') }}
-  labels:
-    addonmanager.kubernetes.io/mode: Reconcile
-data:
-  contrail.conf: |
-    [DEFAULT]
-    master = localhost:8080
-    service-cluster-ip-range = {{ master.service_addresses }}
-
-    [opencontrail]
-    default-domain = {{ master.network.opencontrail.get('default_domain', 'default-domain') }}
-    public-ip-range = {{ master.network.opencontrail.get('public_ip_range', '185.22.97.128/26') }}
-    cluster-service = {{ master.network.opencontrail.get('cluster_service', 'kube-system/default') }}
-    api-server = {{ master.network.opencontrail.config.api.host }}
-    api-port = {{ master.network.opencontrail.get('port', 8082) }}
-    default = {{ master.network.opencontrail.get('default_project', 'default-domain:default-project') }}
-    public-network = {{ master.network.opencontrail.get('public_network', 'default-domain:default-project:Public') }}
-    private-ip-range = {{ master.network.opencontrail.private_ip_range }}
-    network-label = {{ master.network.opencontrail.get('network_label', 'opencontrail.org/name') }}
-    service-label = {{ master.network.opencontrail.get('service_label', 'opencontrail.org/uses') }}
-    service-cluster-ip-range = {{ master.get('service_addresses', '10.254.0.0/16') }}
diff --git a/kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-deploy.yml b/kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-deploy.yml
deleted file mode 100644
index 1615919..0000000
--- a/kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-deploy.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-{%- from "kubernetes/map.jinja" import common with context -%}
-apiVersion: extensions/v1beta1
-kind: Deployment
-metadata:
-  name: contrail-network-controller
-  namespace: {{ common.addons.contrail_network_controller.get('namespace', 'kube-system') }}
-  labels:
-    addonmanager.kubernetes.io/mode: Reconcile
-spec:
-  replicas: 1
-  template:
-    metadata:
-      labels:
-        app: contrail-network-controller
-        addonmanager.kubernetes.io/mode: Reconcile
-    spec:
-      hostNetwork: true
-      tolerations:
-        - key: node-role.kubernetes.io/master
-          effect: NoSchedule
-      nodeSelector:
-        node-role.kubernetes.io/master: 'true'
-      containers:
-        - name: contrail-kube-manager
-          image: {{ common.addons.contrail_network_controller.get('image', 'yashulyak/contrail-controller:latest') }}
-          imagePullPolicy: Always
-          args: ["--config-file", "/etc/kube-manager/contrail.conf", "--alsologtostderr"]
-          volumeMounts:
-          - name: contrail-kube-manager
-            mountPath: /etc/kube-manager/
-      volumes:
-        - name: contrail-kube-manager
-          configMap:
-            name: contrail-kube-manager
-      restartPolicy: Always
diff --git a/kubernetes/files/kube-addons/netchecker/netchecker-server.yml b/kubernetes/files/kube-addons/netchecker/netchecker-server.yml
index f4d28de..997df9b 100644
--- a/kubernetes/files/kube-addons/netchecker/netchecker-server.yml
+++ b/kubernetes/files/kube-addons/netchecker/netchecker-server.yml
@@ -41,9 +41,6 @@
             - "-v=5"
             - "-logtostderr"
             - "-endpoint=0.0.0.0:{{ common.addons.netchecker.port }}"
-{%- if common.addons.get('contrail_network_controller', {}).get('enabled') %}
-            - "-kubeproxyinit=true"
-{%- else %}
             - "-kubeproxyinit=false"
             - "-etcd-endpoints={% for member in master.etcd.members %}https://{{ member.host }}:4001{% if not loop.last %},{% endif %}{% endfor %}"
 {%- if master.etcd.get('ssl', {}).get('enabled') %}
@@ -59,4 +56,3 @@
       - hostPath:
           path: /var/lib/etcd
         name: etcd-certs
-{%- endif %}
diff --git a/kubernetes/files/kube-addons/openstack-cloud-provider/openstack-cloud-provider.yaml b/kubernetes/files/kube-addons/openstack-cloud-provider/openstack-cloud-provider.yaml
new file mode 100644
index 0000000..59a9755
--- /dev/null
+++ b/kubernetes/files/kube-addons/openstack-cloud-provider/openstack-cloud-provider.yaml
@@ -0,0 +1,41 @@
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: openstack-cloud-controller-manager
+  labels:
+    addonmanager.kubernetes.io/mode: Reconcile
+subjects:
+- kind: User
+  name: system:kube-controller-manager
+  apiGroup: rbac.authorization.k8s.io
+roleRef:
+  kind: ClusterRole
+  name: cluster-admin
+  apiGroup: rbac.authorization.k8s.io
+
+---
+kind: InitializerConfiguration
+apiVersion: admissionregistration.k8s.io/v1alpha1
+metadata:
+  name: pvlabel.kubernetes.io
+  labels:
+    addonmanager.kubernetes.io/mode: Reconcile
+initializers:
+  - name: pvlabel.kubernetes.io
+    rules:
+    - apiGroups:
+      - ""
+      apiVersions:
+      - "*"
+      resources:
+      - persistentvolumes
+
+---
+kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: cinder
+  labels:
+    addonmanager.kubernetes.io/mode: Reconcile
+provisioner: kubernetes.io/cinder
diff --git a/kubernetes/files/kube-addons/virtlet/virtlet-ds.yml b/kubernetes/files/kube-addons/virtlet/virtlet-ds.yml
index e9035e9..84d279f 100644
--- a/kubernetes/files/kube-addons/virtlet/virtlet-ds.yml
+++ b/kubernetes/files/kube-addons/virtlet/virtlet-ds.yml
@@ -124,8 +124,6 @@
         - mountPath: /run
           mountPropagation: Bidirectional
           name: run
-        - mountPath: /var/run/docker.sock
-          name: dockersock
         - mountPath: /hostlog
           name: log
         - mountPath: /host-var-lib
@@ -262,9 +260,6 @@
           path: /run
         name: run
       - hostPath:
-          path: /var/run/docker.sock
-        name: dockersock
-      - hostPath:
           path: /var/lib/virtlet
         name: virtlet
       - hostPath:
diff --git a/kubernetes/files/kubeconfig.sh b/kubernetes/files/kubeconfig.sh
index cfdc79b..a88aa93 100644
--- a/kubernetes/files/kubeconfig.sh
+++ b/kubernetes/files/kubeconfig.sh
@@ -5,8 +5,13 @@
 server="$(awk '/server/ { print $2 }' /etc/kubernetes/kubelet.kubeconfig)"
 
 # certificates
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
 cert="$(base64 --wrap=0 /etc/kubernetes/ssl/kubelet-client.crt)"
 key="$(base64 --wrap=0 /etc/kubernetes/ssl/kubelet-client.key)"
+{%- else %}
+cert="$(base64 --wrap=0 /etc/kubernetes/ssl/kubelet-client-fqdn.crt)"
+key="$(base64 --wrap=0 /etc/kubernetes/ssl/kubelet-client-fqdn.key)"
+{%- endif %}
 ca="$(base64 --wrap=0 /etc/kubernetes/ssl/ca-kubernetes.crt )"
 cluster="{{ common.cluster_name }}"
 
diff --git a/kubernetes/files/kubelet/default.master b/kubernetes/files/kubelet/default.master
index 3cdb8bf..5436140 100644
--- a/kubernetes/files/kubelet/default.master
+++ b/kubernetes/files/kubelet/default.master
@@ -25,7 +25,11 @@
 --allow-privileged={{ master.kubelet.allow_privileged }} \
 --cluster_dns={{ common.addons.dns.server }} \
 --cluster_domain={{ common.addons.dns.domain|replace('_', '-') }} \
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+--hostname-override={{ master.host.name }}.{{ common.cluster_domain }} \
+{%- else %}
 --hostname-override={{ master.host.name }} \
+{%- endif %}
 --v={{ master.get('verbosity', 2) }} \
 --node-labels=node-role.kubernetes.io/master=true \
 {%- if common.hyperkube.pause_image is defined %}
@@ -45,10 +49,7 @@
 --register-with-taints=node-role.kubernetes.io/master=:NoSchedule \
 {%- endif %}
 {%- if common.get('cloudprovider', {}).get('enabled') %}
---cloud-provider={{ common.cloudprovider.provider }} \
-{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
---cloud-config=/etc/kubernetes/cloud-config.conf \
-{%- endif %}
+--cloud-provider=external \
 {%- endif %}
 {%- if common.get('containerd', {}).get('enabled') %}
 --container-runtime={{ master.get('container-runtime', 'remote') }} \
diff --git a/kubernetes/files/kubelet/default.pool b/kubernetes/files/kubelet/default.pool
index ee1a5a9..0774eee 100644
--- a/kubernetes/files/kubelet/default.pool
+++ b/kubernetes/files/kubelet/default.pool
@@ -25,7 +25,11 @@
 --allow-privileged={{ pool.kubelet.allow_privileged }} \
 --cluster_dns={{ common.addons.dns.server }} \
 --cluster_domain={{ common.addons.dns.domain|replace('_', '-') }} \
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+--hostname-override={{ pool.host.name }}.{{ common.cluster_domain }} \
+{%- else %}
 --hostname-override={{ pool.host.name }} \
+{%- endif %}
 --v={{ pool.get('verbosity', 2) }} \
 {%- if common.hyperkube.pause_image is defined %}
 --pod-infra-container-image={{ common.hyperkube.pause_image }} \
@@ -51,9 +55,9 @@
 {%- endif %}
 --file-check-frequency={{ pool.kubelet.frequency }} \
 {%- if common.get('cloudprovider', {}).get('enabled') %}
---cloud-provider={{ common.cloudprovider.provider }} \
+--cloud-provider=external \
 {%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
---cloud-config=/etc/kubernetes/cloud-config.conf \
+--cloud-config=/etc/kubernetes/cloud-config \
 {%- endif %}
 {%- endif %}
 {%- if common.addons.get('virtlet', {}).get('enabled') %}
diff --git a/kubernetes/files/kubelet/kubelet.kubeconfig.master b/kubernetes/files/kubelet/kubelet.kubeconfig.master
index 3c70ded..810a129 100644
--- a/kubernetes/files/kubelet/kubelet.kubeconfig.master
+++ b/kubernetes/files/kubelet/kubelet.kubeconfig.master
@@ -17,5 +17,10 @@
 users:
 - name: kubelet-{{ common.cluster_name }}
   user:
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+    client-certificate: /etc/kubernetes/ssl/kubelet-client-fqdn.crt
+    client-key: /etc/kubernetes/ssl/kubelet-client-fqdn.key
+{%- else %}
     client-certificate: /etc/kubernetes/ssl/kubelet-client.crt
     client-key: /etc/kubernetes/ssl/kubelet-client.key
+{%- endif %}
diff --git a/kubernetes/files/kubelet/kubelet.kubeconfig.pool b/kubernetes/files/kubelet/kubelet.kubeconfig.pool
index 3228ea6..927d419 100644
--- a/kubernetes/files/kubelet/kubelet.kubeconfig.pool
+++ b/kubernetes/files/kubelet/kubelet.kubeconfig.pool
@@ -17,5 +17,10 @@
 users:
 - name: kubelet-{{ common.cluster_name }}
   user:
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+    client-certificate: /etc/kubernetes/ssl/kubelet-client-fqdn.crt
+    client-key: /etc/kubernetes/ssl/kubelet-client-fqdn.key
+{%- else %}
     client-certificate: /etc/kubernetes/ssl/kubelet-client.crt
     client-key: /etc/kubernetes/ssl/kubelet-client.key
+{%- endif %}
diff --git a/kubernetes/files/systemd/openstack-cloud-controller-manager.service b/kubernetes/files/systemd/openstack-cloud-controller-manager.service
new file mode 100644
index 0000000..98b54db
--- /dev/null
+++ b/kubernetes/files/systemd/openstack-cloud-controller-manager.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=OpenStack Cloud Controller Manager
+Documentation=https://github.com/kubernetes/cloud-provider-openstack
+Documentation=man:openstack-cloud-controller-manager
+After=network.target
+
+[Service]
+SyslogIdentifier=openstack-cloud-controller-manager
+EnvironmentFile=-/etc/kubernetes/config
+EnvironmentFile=-/etc/default/%p
+User=root
+ExecStart=/usr/bin/openstack-cloud-controller-manager \
+    $KUBE_LOGTOSTDERR \
+    $KUBE_LOG_LEVEL \
+        $DAEMON_ARGS
+Restart=on-failure
+LimitNOFILE=65536
+
+[Install]
+WantedBy=multi-user.target
diff --git a/kubernetes/map.jinja b/kubernetes/map.jinja
index ad1a7a8..5d1f39f 100644
--- a/kubernetes/map.jinja
+++ b/kubernetes/map.jinja
@@ -1,5 +1,5 @@
 {% set version = salt['cmd.shell']("(hyperkube --version kubelet 2> /dev/null || echo '0.0') | sed -e 's/-.*//g' -e 's/v//g' -e 's/Kubernetes //g' | awk -F'.' '{print $1 \".\" $2}'") %}
-{% set full_version = salt['cmd.shell']("(hyperkube --version kubelet 2> /dev/null || echo '0.0') | sed -e 's/-.*//g' -e 's/v//g' -e 's/Kubernetes //g'") %}
+{% set full_version = salt['cmd.shell']("(hyperkube --version kubelet 2> /dev/null || echo '0.0') | sed -e 's/+.*//g' -e 's/v//g' -e 's/Kubernetes //g'") %}
 
 {% set common = salt['grains.filter_by']({
     'Debian': {
@@ -95,48 +95,6 @@
     },
 }, merge=salt['pillar.get']('kubernetes:control')) %}
 
-{%- if master.get('network', {}).get('engine') == 'calico' %}
-{% set calico = salt['grains.filter_by']({
-    'default': master.network.calico
-}, merge=salt['pillar.get']('kubernetes:master:network')) %}
-{% do calico.update({
-    'enabled': true,
-    'calicoctl_image': calico.calicoctl.image,
-    'cni_image': calico.cni.image,
-}) %}
-{% do master.network.calico.update(calico) %}
-{%- elif master.get('network', {}).get('engine') == 'opencontrail' %}
-{% set opencontrail = salt['grains.filter_by']({
-    'default': master.network.opencontrail
-}, merge=salt['pillar.get']('kubernetes:master:network')) %}
-{% do opencontrail.update({
-    'enabled': true,
-    'cni_image': opencontrail.contrail_cni.image,
-}) %}
-{% do master.network.opencontrail.update(opencontrail) %}
-{%- endif %}
-
-{%- if pool.get('network', {}).get('engine') == 'calico' %}
-{% set calico = salt['grains.filter_by']({
-    'default': pool.network.calico
-}, merge=salt['pillar.get']('kubernetes:pool:network')) %}
-{% do calico.update({
-    'enabled': true,
-    'calicoctl_image': calico.calicoctl.image,
-    'cni_image': calico.cni.image,
-}) %}
-{% do pool.network.calico.update(calico) %}
-{%- elif pool.get('network', {}).get('engine') == 'opencontrail' %}
-{% set opencontrail = salt['grains.filter_by']({
-    'default': pool.network.opencontrail
-}, merge=salt['pillar.get']('kubernetes:pool:network')) %}
-{% do opencontrail.update({
-    'enabled': true,
-    'cni_image': opencontrail.contrail_cni.image,
-}) %}
-{% do pool.network.opencontrail.update(opencontrail) %}
-{%- endif %}
-
 {%- set monitoring = salt['grains.filter_by']({
   'default': {
     'instance_minor_threshold_percent': 0.3,
diff --git a/kubernetes/master/calico.sls b/kubernetes/master/calico.sls
index 4d0fb48..8f8b161 100644
--- a/kubernetes/master/calico.sls
+++ b/kubernetes/master/calico.sls
@@ -1,3 +1,4 @@
+{%- from "kubernetes/map.jinja" import common with context %}
 {%- from "kubernetes/map.jinja" import master with context %}
 {%- if master.enabled %}
 
@@ -21,40 +22,48 @@
     - dir_mode: 755
     - template: jinja
 
-/tmp/calico/:
-  file.directory:
-      - user: root
-      - group: root
-
-copy-calico-ctl:
-  cmd.run:
-    - name: docker run --rm -v /tmp/calico/:/tmp/calico/ --entrypoint cp {{ master.network.calico.calicoctl_image }} -v /calicoctl /tmp/calico/
-    - require:
-      - file: /tmp/calico/
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
-
 /usr/bin/calicoctl:
   file.managed:
-    - source: /tmp/calico/calicoctl
+    - source: {{ master.network.calico.calicoctl_source }}
+    - source_hash: {{ master.network.calico.calicoctl_source_hash }}
     - mode: 751
     - user: root
     - group: root
-    - require:
-      - cmd: copy-calico-ctl
     {%- if grains.get('noservices') %}
     - onlyif: /bin/false
     {%- endif %}
-
 {%- if master.network.calico.get('systemd', true) %}
 
+{%- if common.get('containerd', {}).get('enabled', false) %}
+/etc/systemd/system/calico-node.service:
+  file.managed:
+    - source: salt://kubernetes/files/calico/calico-node.service.ctr
+    - user: root
+    - group: root
+    - template: jinja
+    - require:
+      - service: containerd
+    - defaults:
+      hostname: {{ master.host.name }}{% if master.host.get('domain') %}.{{ master.host.domain }}{%- endif %}
+      address: {{ master.apiserver.address }}
+      calico: {{ master.network.calico }}
+{%- else %}
 /etc/systemd/system/calico-node.service:
   file.managed:
     - source: salt://kubernetes/files/calico/calico-node.service.master
     - user: root
     - group: root
     - template: jinja
+{%- endif %}
+
+{%- for dirname in ['lib', 'log'] %}
+/var/{{ dirname }}/calico:
+  file.directory:
+      - user: root
+      - group: root
+      - require_in:
+        - service: calico-node
+{%- endfor %}
 
 calico_node:
   service.running:
diff --git a/kubernetes/master/controller.sls b/kubernetes/master/controller.sls
index 83f752d..54bcf34 100644
--- a/kubernetes/master/controller.sls
+++ b/kubernetes/master/controller.sls
@@ -88,6 +88,42 @@
 
 {%- else %}
 
+
+{%- if common.get('cloudprovider', {}).get('enabled') %}
+{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+/usr/bin/openstack-cloud-controller-manager:
+  file.managed:
+    - source: {{ common.cloudprovider.params.binary }}
+    - mode: 751
+    - makedirs: true
+    - user: root
+    - group: root
+    - source_hash: {{ common.cloudprovider.params.binary_hash }}
+
+/etc/default/openstack-cloud-controller-manager:
+  file.managed:
+    - user: root
+    - group: root
+    - mode: 644
+    - contents: >-
+        DAEMON_ARGS="
+        --cloud-provider=openstack
+        --cloud-config /etc/kubernetes/cloud-config
+        --cluster-name=kubernetes
+        --kubeconfig /etc/kubernetes/controller-manager.kubeconfig
+        --leader-elect=true
+        --v={{ master.get('verbosity', 2) }}"
+
+/etc/systemd/system/openstack-cloud-controller-manager.service:
+  file.managed:
+  - source: salt://kubernetes/files/systemd/openstack-cloud-controller-manager.service
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 644
+{%- endif %}
+{%- endif %}
+
 /etc/default/kube-apiserver:
   file.managed:
     - user: root
@@ -97,7 +133,15 @@
         # Using hyperkube version v{{ full_version }}
 
         DAEMON_ARGS="
-        --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,DefaultStorageClass
+        {%- if common.get('cloudprovider', {}).get('enabled') %}
+        {%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+        --runtime-config=admissionregistration.k8s.io/v1alpha1
+        --enable-admission-plugins=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,DefaultStorageClass
+        --disable-admission-plugins=PersistentVolumeLabel
+        {%- endif %}
+        {%- else %}
+        --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,DefaultStorageClass
+        {%- endif %}
         --allow-privileged=True
         {%- if master.auth.get('mode') %}
         --authorization-mode={{ master.auth.mode }}
@@ -140,12 +184,6 @@
 {%- if master.apiserver.node_port_range is defined %}
         --service-node-port-range {{ master.apiserver.node_port_range }}
 {%- endif %}
-{%- if common.get('cloudprovider', {}).get('enabled') %}
-        --cloud-provider={{ common.cloudprovider.provider }}
-{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
-        --cloud-config=/etc/kubernetes/cloud-config.conf
-{%- endif %}
-{%- endif %}
 {%- if common.addons.get('virtlet', {}).get('enabled') %}
 {%- if salt['pkg.version_cmp'](version,'1.8') >= 0 %}
         --feature-gates=MountPropagation=true
@@ -191,12 +229,6 @@
         --root-ca-file=/etc/kubernetes/ssl/ca-{{ master.ca }}.crt
         --service-account-private-key-file=/etc/kubernetes/ssl/kubernetes-server.key
         --use-service-account-credentials
-{%- if common.get('cloudprovider', {}).get('enabled') %}
-        --cloud-provider={{ common.cloudprovider.provider }}
-{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
-        --cloud-config=/etc/kubernetes/cloud-config.conf
-{%- endif %}
-{%- endif %}
         --v={{ master.get('verbosity', 2) }}
 {%- if master.network.get('flannel', {}).get('enabled', False) %}
         --allocate-node-cidrs=true
@@ -273,6 +305,19 @@
     - file: /etc/default/kube-controller-manager
     - file: /usr/bin/hyperkube
 
+{%- if common.get('cloudprovider', {}).get('enabled') %}
+{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+openstack_cloud_controller_service:
+  service.running:
+  - name: openstack-cloud-controller-manager
+  - enable: True
+  - watch:
+    - file: /etc/kubernetes/cloud-config
+    - file: /etc/default/openstack-cloud-controller-manager
+    - file: /etc/kubernetes/controller-manager.kubeconfig
+{%- endif %}
+{%- endif %}
+
 {%- endif %}
 
 
diff --git a/kubernetes/master/kube-addons.sls b/kubernetes/master/kube-addons.sls
index 20d05c3..2d5dc1b 100644
--- a/kubernetes/master/kube-addons.sls
+++ b/kubernetes/master/kube-addons.sls
@@ -30,24 +30,6 @@
     - makedirs: True
 {% endif %}
 
-{%- if master.network.get('opencontrail', {}).get('enabled', False) and master.network.opencontrail.get('version', 3.0) < 4.0 %}
-/etc/kubernetes/addons/contrail-network-controller/contrail-network-controller-configmap.yml:
-  file.managed:
-    - source: salt://kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-configmap.yml
-    - template: jinja
-    - group: root
-    - dir_mode: 755
-    - makedirs: True
-
-/etc/kubernetes/addons/contrail-network-controller/contrail-network-controller-deploy.yml:
-  file.managed:
-    - source: salt://kubernetes/files/kube-addons/contrail-network-controller/contrail-network-controller-deploy.yml
-    - template: jinja
-    - group: root
-    - dir_mode: 755
-    - makedirs: True
-{% endif %}
-
 {%- if common.addons.get('virtlet', {}).get('enabled') %}
 /etc/kubernetes/addons/virtlet/virtlet-ds.yml:
   file.managed:
@@ -515,4 +497,16 @@
     - makedirs: True
 {% endif %}
 
+{%- if common.get('cloudprovider', {}).get('enabled') %}
+{%- if common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+/etc/kubernetes/addons/openstack-cloud-provider/openstack-cloud-provider.yaml:
+  file.managed:
+    - source: salt://kubernetes/files/kube-addons/openstack-cloud-provider/openstack-cloud-provider.yaml
+    - template: jinja
+    - group: root
+    - dir_mode: 755
+    - makedirs: True
+{% endif %}
+{% endif %}
+
 {% endif %}
diff --git a/kubernetes/master/setup.sls b/kubernetes/master/setup.sls
index 54a98c2..7eff28d 100644
--- a/kubernetes/master/setup.sls
+++ b/kubernetes/master/setup.sls
@@ -61,6 +61,28 @@
   - onlyif: /bin/false
   {%- endif %}
 
+/srv/kubernetes/conformance.yml:
+  file.managed:
+    - source: salt://kubernetes/files/conformance/conformance.yml
+    - template: jinja
+    - user: root
+    - group: root
+    - mode: 644
+    - makedirs: True
+
+{%- if common.addons.get('virtlet', {}).get('enabled') %}
+
+/srv/kubernetes/virtlet_conformance.yml:
+  file.managed:
+    - source: salt://kubernetes/files/conformance/virtlet_conformance.yml
+    - template: jinja
+    - user: root
+    - group: root
+    - mode: 644
+    - makedirs: True
+
+{%- endif %}
+
 {%- if master.label is defined %}
 
 {%- for name,label in master.label.items() %}
diff --git a/kubernetes/meta/collectd.yml b/kubernetes/meta/collectd.yml
index 8237e25..0972dcb 100644
--- a/kubernetes/meta/collectd.yml
+++ b/kubernetes/meta/collectd.yml
@@ -1,4 +1,5 @@
 {%- from "kubernetes/map.jinja" import master with context %}
+{%- from "kubernetes/map.jinja" import common with context -%}
 {%- from "kubernetes/map.jinja" import pool with context %}
 
 {%- if pool.get('enabled', False) %}
@@ -99,8 +100,13 @@
        # alternative names DNS entries.
        # https://github.com/shazow/urllib3/issues/258
        verify: false
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+       client_cert: /etc/kubernetes/ssl/kubelet-client-fqdn.crt
+       client_key: /etc/kubernetes/ssl/kubelet-client-fqdn.key
+{%- else %}
        client_cert: /etc/kubernetes/ssl/kubelet-client.crt
        client_key: /etc/kubernetes/ssl/kubelet-client.key
+{%- endif %}
        url: https://{{ pool.apiserver.host }}:{{ pool.apiserver.secure_port }}/healthz
        metric_name: k8s_service_health_vip
   collectd_k8s_get:
@@ -109,7 +115,12 @@
    polling_interval: 60
    interval: 30
    verify: false
+{%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+   client_cert: /etc/kubernetes/ssl/kubelet-client-fqdn.crt
+   client_key: /etc/kubernetes/ssl/kubelet-client-fqdn.key
+{%- else %}
    client_cert: /etc/kubernetes/ssl/kubelet-client.crt
    client_key: /etc/kubernetes/ssl/kubelet-client.key
+{%- endif %}
    endpoint: https://{{ pool.apiserver.host }}:{{ pool.apiserver.secure_port }}
 {%- endif %}
diff --git a/kubernetes/meta/fluentd.yml b/kubernetes/meta/fluentd.yml
index 9c66b75..c881fd9 100644
--- a/kubernetes/meta/fluentd.yml
+++ b/kubernetes/meta/fluentd.yml
@@ -1,3 +1,4 @@
+{%- from "kubernetes/map.jinja" import common with context -%}
 {%- if pillar.get('fluentd', {}).get('agent', {}).get('enabled', False) %}
 {%- from "kubernetes/map.jinja" import pool, master %}
 {%- if pool.get('enabled', False) %}
@@ -79,8 +80,13 @@
             tag: 'temp.kubernetes.container.**'
             type: kubernetes_metadata
             kubernetes_url: https://{{ pool.apiserver.host }}:{{ pool.apiserver.secure_port }}
+            {%- if common.get('cloudprovider', {}).get('enabled') and common.get('cloudprovider', {}).get('provider') == 'openstack' %}
+            client_cert: /etc/kubernetes/ssl/kubelet-client-fqdn.crt
+            client_key: /etc/kubernetes/ssl/kubelet-client-fqdn.key
+            +{%- else %}
             client_cert: /etc/kubernetes/ssl/kubelet-client.crt
             client_key: /etc/kubernetes/ssl/kubelet-client.key
+            +{%- endif %}
             ca_file: /etc/kubernetes/ssl/ca-kubernetes.crt
             verify_ssl: True
           enrich_container:
diff --git a/kubernetes/pool/calico.sls b/kubernetes/pool/calico.sls
index 30f4956..cd99513 100644
--- a/kubernetes/pool/calico.sls
+++ b/kubernetes/pool/calico.sls
@@ -1,80 +1,56 @@
+{%- from "kubernetes/map.jinja" import common with context %}
 {%- from "kubernetes/map.jinja" import pool with context %}
 {%- if pool.enabled %}
 
-/tmp/calico/:
-  file.directory:
-      - user: root
-      - group: root
-
-copy-calico-ctl:
-  cmd.run:
-    - name: docker run --rm -v /tmp/calico/:/tmp/calico/ --entrypoint cp {{ pool.network.calico.calicoctl_image }} -v /calicoctl /tmp/calico/
-    - require:
-      - file: /tmp/calico/
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
-
 /usr/bin/calicoctl:
   file.managed:
-    - source: /tmp/calico/calicoctl
+    - source: {{ pool.network.calico.calicoctl_source }}
+    - source_hash: {{ pool.network.calico.calicoctl_source_hash }}
     - mode: 751
     - user: root
     - group: root
-    - require:
-      - cmd: copy-calico-ctl
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
-
-copy-calico-node:
-  cmd.run:
-    - name: docker run --rm -v /tmp/calico/:/tmp/calico/ --entrypoint cp {{ pool.network.calico.image }} -v /bin/birdcl /tmp/calico/
-    - require:
-      - file: /tmp/calico/
     {%- if grains.get('noservices') %}
     - onlyif: /bin/false
     {%- endif %}
 
 /usr/bin/birdcl:
   file.managed:
-    - source: /tmp/calico/birdcl
+    - source: {{ pool.network.calico.birdcl_source }}
+    - source_hash: {{ pool.network.calico.birdcl_source_hash }}
     - mode: 751
     - user: root
     - group: root
-    - require:
-      - cmd: copy-calico-node
     {%- if grains.get('noservices') %}
     - onlyif: /bin/false
     {%- endif %}
 
-copy-calico-cni:
-  cmd.run:
-    - name: docker run --rm -v /tmp/calico/:/tmp/calico/ --entrypoint cp {{ pool.network.calico.cni_image }} -vr /opt/cni/bin/ /tmp/calico/
-    - require:
-      - file: /tmp/calico/
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
-
-{%- for filename in ['calico', 'calico-ipam'] %}
-
-/opt/cni/bin/{{ filename }}:
+/opt/cni/bin/calico:
   file.managed:
-    - source: /tmp/calico/bin/{{ filename }}
+    - source: {{ pool.network.calico.cni_source }}
+    - source_hash: {{ pool.network.calico.cni_source_hash }}
     - mode: 751
     - makedirs: true
     - user: root
     - group: root
-    - require:
-      - cmd: copy-calico-cni
     - require_in:
       - service: calico_node
     {%- if grains.get('noservices') %}
     - onlyif: /bin/false
     {%- endif %}
 
-{%- endfor %}
+/opt/cni/bin/calico-ipam:
+  file.managed:
+    - source: {{ pool.network.calico.cni_ipam_source }}
+    - source_hash: {{ pool.network.calico.cni_ipam_source_hash }}
+    - mode: 751
+    - makedirs: true
+    - user: root
+    - group: root
+    - require_in:
+      - service: calico_node
+    {%- if grains.get('noservices') %}
+    - onlyif: /bin/false
+    {%- endif %}
 
 /etc/cni/net.d/10-calico.conf:
   file.managed:
@@ -108,12 +84,36 @@
 
 {%- if pool.network.calico.get('systemd', true) %}
 
+{%- if common.get('containerd', {}).get('enabled', false) %}
+/etc/systemd/system/calico-node.service:
+  file.managed:
+    - source: salt://kubernetes/files/calico/calico-node.service.ctr
+    - user: root
+    - group: root
+    - template: jinja
+    - require:
+      - service: containerd
+    - defaults:
+      hostname: {{ pool.host.name }}{% if pool.host.get('domain') %}.{{ pool.host.domain }}{%- endif %}
+      address: {{ pool.address }}
+      calico: {{ pool.network.calico }}
+{%- else %}
 /etc/systemd/system/calico-node.service:
   file.managed:
     - source: salt://kubernetes/files/calico/calico-node.service.pool
     - user: root
     - group: root
     - template: jinja
+{%- endif %}
+
+{%- for dirname in ['lib', 'log'] %}
+/var/{{ dirname }}/calico:
+  file.directory:
+      - user: root
+      - group: root
+      - require_in:
+        - service: calico-node
+{%- endfor %}
 
 calico_node:
   service.running:
diff --git a/kubernetes/pool/opencontrail.sls b/kubernetes/pool/opencontrail.sls
index 255861b..54dafd0 100644
--- a/kubernetes/pool/opencontrail.sls
+++ b/kubernetes/pool/opencontrail.sls
@@ -11,37 +11,6 @@
     - dir_mode: 755
     - template: jinja
 
-{%- if pool.network.opencontrail.get('version', '3.0') == '3.0' %}
-
-/tmp/opencontrail:
-  file.directory:
-    - user: root
-    - group: root
-
-copy-contrail-cni:
-  cmd.run:
-    - name: docker cp $(docker create  {{ pool.network.opencontrail.cni_image }}):/opencontrail /tmp/opencontrail
-    - require:
-      - file: /tmp/opencontrail
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
-
-/opt/cni/bin/opencontrail:
-  file.managed:
-    - source: /tmp/opencontrail/opencontrail
-    - mode: 755
-    - makedirs: true
-    - user: root
-    - group: root
-    - require:
-      - cmd: copy-contrail-cni
-    {%- if grains.get('noservices') %}
-    - onlyif: /bin/false
-    {%- endif %}
-
-{%- else %}
-
 opencontrail_cni_package:
   pkg.installed:
   - name: contrail-k8s-cni
@@ -59,5 +28,3 @@
     - pkg: opencontrail_cni_package
 
 {%- endif %}
-
-{%- endif %}
diff --git a/metadata/service/common.yml b/metadata/service/common.yml
index 877a6a1..95e9c77 100644
--- a/metadata/service/common.yml
+++ b/metadata/service/common.yml
@@ -45,19 +45,6 @@
           port: 80
           agent_image: mirantis/k8s-netchecker-agent:stable
           server_image: mirantis/k8s-netchecker-server:stable
-        contrail_network_controller:
-          enabled: False
-          namespace: kube-system
-          image: yashulyak/contrail-controller:latest
-        opencontrail:
-          controller:
-            image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-controller:latest
-          analyticsdb:
-            image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-analyticsdb:latest
-          analytics:
-            image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-analytics:latest
-          kube_manager:
-            image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-kube-manager:latest
         coredns:
           enabled: False
           namespace: kube-system
@@ -150,6 +137,6 @@
         provider: openstack
         params:
           region: RegionOne
-          domain_name: default
+          domain_id: default
       cluster_domain: ${_param:kubernetes_cluster_domain}
       cluster_name: ${_param:cluster_name}
diff --git a/metadata/service/master/single.yml b/metadata/service/master/single.yml
index f00bd2b..be5d701 100644
--- a/metadata/service/master/single.yml
+++ b/metadata/service/master/single.yml
@@ -51,10 +51,6 @@
         helm:
           enabled: False
           tiller_image: gcr.io/kubernetes-helm/tiller:v2.4.2
-        contrail_network_controller:
-          enabled: False
-          namespace: kube-system
-          image: yashulyak/contrail-controller:latest
         virtlet:
           enabled: False
           namespace: kube-system
diff --git a/tests/pillar/common_storageclass.sls b/tests/pillar/common_storageclass.sls
index 7d6e97d..91e6b04 100644
--- a/tests/pillar/common_storageclass.sls
+++ b/tests/pillar/common_storageclass.sls
@@ -23,8 +23,8 @@
     network:
       calico:
         enabled: true
-        calicoctl_image: calico/ctl
-        cni_image: calico/cni
+        calicoctl_source: calico/ctl
+        calicoctl_source_hash: d41d8cd98f00b204e9800998ecf8427e
         image: calico/node
         kube_controllers_image: calico/kube-controllers
         etcd:
diff --git a/tests/pillar/master_cluster.sls b/tests/pillar/master_cluster.sls
index a61ce7e..6a6a85c 100644
--- a/tests/pillar/master_cluster.sls
+++ b/tests/pillar/master_cluster.sls
@@ -4,7 +4,8 @@
     cluster_name: cluster
     hyperkube:
       image: hyperkube-amd64:v1.6.4-3
-      hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
+      source: hyperkube-amd64:v1.6.4-3
+      source_hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
     addons:
       dns:
         domain: cluster.local
@@ -63,8 +64,8 @@
     network:
       calico:
         enabled: true
-        calicoctl_image: calico/ctl
-        cni_image: calico/cni
+        calicoctl_source: calico/ctl
+        calicoctl_source_hash: d41d8cd98f00b204e9800998ecf8427e
         image: calico/node
         kube_controllers_image: calico/kube-controllers
         etcd:
diff --git a/tests/pillar/master_contrail.sls b/tests/pillar/master_contrail.sls
deleted file mode 100644
index ced09cb..0000000
--- a/tests/pillar/master_contrail.sls
+++ /dev/null
@@ -1,118 +0,0 @@
-kubernetes:
-  common:
-    cluster_domain: cluster.local
-    cluster_name: cluster
-    hyperkube:
-      image: hyperkube-amd64:v1.6.4-3
-      hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
-    addons:
-      dns:
-        domain: cluster.local
-        enabled: false
-        replicas: 1
-        server: 10.254.0.10
-        autoscaler:
-          enabled: true
-      heapster_influxdb:
-        enabled: true
-        public_ip: 185.22.97.132
-      dashboard:
-        enabled: true
-        public_ip: 185.22.97.131
-      helm:
-        enabled: true
-        tiller_image: gcr.io/kubernetes-helm/tiller:v2.2.3
-      netchecker:
-        enabled: true
-        namespace: netchecker
-        port: 80
-        interval: 60
-        server_image: image
-        agent_image: image
-        agent_probeurls: "http://ipinfo.io"
-      virtlet:
-        enabled: true
-        namespace: kube-system
-        image: mirantis/virtlet:v1.0.3
-      contrail_network_controller:
-        enabled: true
-        namespace: kube-system
-        image: yashulyak/contrail-controller:latest
-      opencontrail:
-        controller:
-          image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-controller:latest
-        analyticsdb:
-          image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-analyticsdb:latest
-        analytics:
-          image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-analytics:latest
-        kube_manager:
-          image: docker-prod-local.artifactory.mirantis.com/opencontrail-oc40/opencontrail-kube-manager:latest
-    monitoring:
-      backend: prometheus
-  master:
-    admin:
-      password: password
-      username: admin
-    registry:
-        host: tcpcloud
-    host:
-      name: node040
-    apiserver:
-      address: 10.0.175.100
-      secure_port: 6443
-      internal_address: 185.22.97.1
-      insecure_address: 127.0.0.1
-      insecure_port: 8080
-    ca: kubernetes
-    enabled: true
-    unschedulable: true
-    etcd:
-      members:
-      - host: 10.0.175.100
-        name: node040
-    kubelet:
-      address: 10.0.175.100
-      allow_privileged: true
-    network:
-      opencontrail:
-        enabled: true
-        config:
-          api:
-            host: 10.0.170.70
-        port: 8082
-        default_domain: default-domain
-        default_project: default-domain:default-project
-        public_network: default-domain:default-project:Public
-        public_ip_range: 185.22.97.128/26
-        private_ip_range: 10.150.0.0/16
-        service_cluster_ip_range: 10.254.0.0/16
-        network_label: name
-        service_label: uses
-        cluster_service: kube-system/default
-        image: tianon/true
-    service_addresses: 10.254.0.0/16
-    storage:
-      engine: glusterfs
-      members:
-      - host: 10.0.175.101
-        port: 24007
-      - host: 10.0.175.102
-        port: 24007
-      - host: 10.0.175.103
-        port: 24007
-      port: 24007
-    token:
-      admin: DFvQ8GJ9JD4fKNfuyEddw3rjnFTkUKsv
-      controller_manager: EreGh6AnWf8DxH8cYavB2zS029PUi7vx
-      dns: RAFeVSE4UvsCz4gk3KYReuOI5jsZ1Xt3
-      kube_proxy: DFvQ8GelB7afH3wClC9romaMPhquyyEe
-      kubelet: 7bN5hJ9JD4fKjnFTkUKsvVNfuyEddw3r
-      logging: MJkXKdbgqRmTHSa2ykTaOaMykgO6KcEf
-      monitoring: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
-      scheduler: HY1UUxEPpmjW4a1dDLGIANYQp1nZkLDk
-    version: v1.2.4
-    namespace:
-      kube-system:
-        enabled: true
-      netchecker:
-        enabled: true
diff --git a/tests/pillar/master_contrail4_0.sls b/tests/pillar/master_contrail4_0.sls
index 3c5997d..ce6e5ed 100644
--- a/tests/pillar/master_contrail4_0.sls
+++ b/tests/pillar/master_contrail4_0.sls
@@ -4,7 +4,8 @@
     cluster_name: cluster
     hyperkube:
       image: hyperkube-amd64:v1.6.4-3
-      hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
+      source: hyperkube-amd64:v1.6.4-3
+      source_hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
     addons:
       dns:
         domain: cluster.local
diff --git a/tests/pillar/pool_cluster.sls b/tests/pillar/pool_cluster.sls
index 5f34de4..6a3e05d 100644
--- a/tests/pillar/pool_cluster.sls
+++ b/tests/pillar/pool_cluster.sls
@@ -4,7 +4,8 @@
     cluster_name: cluster
     hyperkube:
       image: hyperkube-amd64:v1.6.4-3
-      hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
+      source: hyperkube-amd64:v1.6.4-3
+      source_hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
     addons:
       dns:
         domain: cluster.local
@@ -47,8 +48,14 @@
     network:
       calico:
         enabled: true
-        calicoctl_image: calico/ctl
-        cni_image: calico/cni
+        calicoctl_source: calico/ctl
+        calicoctl_source_hash: d41d8cd98f00b204e9800998ecf8427e
+        birdcl_source: calico/birdcl
+        birdcl_source_hash: d41d8cd98f00b204e9800998ecf8427e
+        cni_source: calico/cni
+        cni_source_hash: d41d8cd98f00b204e9800998ecf8427e
+        cni_ipam_source: calico/cni-ipam
+        cni_ipam_source_hash: d41d8cd98f00b204e9800998ecf8427e
         image: calico/node
         kube_controllers_image: calico/kube-controllers
         etcd:
diff --git a/tests/pillar/pool_cluster_with_domain.sls b/tests/pillar/pool_cluster_with_domain.sls
index 4bfc733..3567b40 100644
--- a/tests/pillar/pool_cluster_with_domain.sls
+++ b/tests/pillar/pool_cluster_with_domain.sls
@@ -4,7 +4,8 @@
     cluster_name: cluster
     hyperkube:
       image: hyperkube-amd64:v1.6.4-3
-      hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
+      source: hyperkube-amd64:v1.6.4-3
+      source_hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
     addons:
       dns:
         domain: cluster.local
@@ -48,8 +49,14 @@
     network:
       calico:
         enabled: true
-        calicoctl_image: calico/ctl
-        cni_image: calico/cni
+        calicoctl_source: calico/ctl
+        calicoctl_source_hash: d41d8cd98f00b204e9800998ecf8427e
+        birdcl_source: calico/birdcl
+        birdcl_source_hash: d41d8cd98f00b204e9800998ecf8427e
+        cni_source: calico/cni
+        cni_source_hash: d41d8cd98f00b204e9800998ecf8427e
+        cni_ipam_source: calico/cni-ipam
+        cni_ipam_source_hash: d41d8cd98f00b204e9800998ecf8427e
         image: calico/node
         kube_controllers_image: calico/kube-controllers
         etcd:
diff --git a/tests/pillar/pool_contrail4_0.sls b/tests/pillar/pool_contrail4_0.sls
index cdb6c6f..c01a85c 100644
--- a/tests/pillar/pool_contrail4_0.sls
+++ b/tests/pillar/pool_contrail4_0.sls
@@ -4,7 +4,8 @@
     cluster_name: cluster
     hyperkube:
       image: hyperkube-amd64:v1.6.4-3
-      hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
+      source: hyperkube-amd64:v1.6.4-3
+      source_hash: hnsj0XqABgrSww7Nqo7UVTSZLJUt2XRd
     addons:
       dns:
         domain: cluster.local