github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/caas/kubernetes/provider/k8s.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"text/template"
    15  	"time"
    16  
    17  	jujuclock "github.com/juju/clock"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/loggo"
    20  	"github.com/juju/utils/arch"
    21  	"github.com/juju/utils/keyvalues"
    22  	"github.com/juju/utils/set"
    23  	"gopkg.in/juju/names.v2"
    24  	apps "k8s.io/api/apps/v1"
    25  	core "k8s.io/api/core/v1"
    26  	"k8s.io/api/extensions/v1beta1"
    27  	k8sstorage "k8s.io/api/storage/v1"
    28  	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    29  	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    30  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/fields"
    34  	"k8s.io/apimachinery/pkg/util/intstr"
    35  	"k8s.io/apimachinery/pkg/util/yaml"
    36  	"k8s.io/apimachinery/pkg/watch"
    37  	"k8s.io/client-go/kubernetes"
    38  	"k8s.io/client-go/rest"
    39  
    40  	"github.com/juju/juju/agent"
    41  	"github.com/juju/juju/caas"
    42  	"github.com/juju/juju/cloudconfig/podcfg"
    43  	"github.com/juju/juju/core/application"
    44  	"github.com/juju/juju/core/devices"
    45  	"github.com/juju/juju/core/status"
    46  	"github.com/juju/juju/core/watcher"
    47  	"github.com/juju/juju/environs"
    48  	"github.com/juju/juju/environs/config"
    49  	"github.com/juju/juju/environs/context"
    50  	"github.com/juju/juju/juju/paths"
    51  	"github.com/juju/juju/network"
    52  	"github.com/juju/juju/storage"
    53  )
    54  
    55  var logger = loggo.GetLogger("juju.kubernetes.provider")
    56  
    57  const (
    58  	labelOperator    = "juju-operator"
    59  	labelStorage     = "juju-storage"
    60  	labelVersion     = "juju-version"
    61  	labelApplication = "juju-application"
    62  	labelModel       = "juju-model"
    63  
    64  	defaultOperatorStorageClassName = "juju-operator-storage"
    65  
    66  	gpuAffinityNodeSelectorKey = "gpu"
    67  )
    68  
    69  var defaultPropagationPolicy = v1.DeletePropagationForeground
    70  
    71  type kubernetesClient struct {
    72  	clock jujuclock.Clock
    73  	kubernetes.Interface
    74  	apiextensionsClient apiextensionsclientset.Interface
    75  
    76  	// namespace is the k8s namespace to use when
    77  	// creating k8s resources.
    78  	namespace string
    79  
    80  	lock   sync.Mutex
    81  	envCfg *config.Config
    82  
    83  	// modelUUID is the UUID of the model this client acts on.
    84  	modelUUID string
    85  
    86  	// newWatcher is the k8s watcher generator.
    87  	newWatcher NewK8sWatcherFunc
    88  }
    89  
    90  // To regenerate the mocks for the kubernetes Client used by this broker,
    91  // run "go generate" from the package directory.
    92  //go:generate mockgen -package mocks -destination mocks/k8sclient_mock.go k8s.io/client-go/kubernetes Interface
    93  //go:generate mockgen -package mocks -destination mocks/appv1_mock.go k8s.io/client-go/kubernetes/typed/apps/v1 AppsV1Interface,DeploymentInterface,StatefulSetInterface
    94  //go:generate mockgen -package mocks -destination mocks/corev1_mock.go k8s.io/client-go/kubernetes/typed/core/v1 CoreV1Interface,NamespaceInterface,PodInterface,ServiceInterface,ConfigMapInterface,PersistentVolumeInterface,PersistentVolumeClaimInterface,SecretInterface,NodeInterface
    95  //go:generate mockgen -package mocks -destination mocks/extenstionsv1_mock.go k8s.io/client-go/kubernetes/typed/extensions/v1beta1 ExtensionsV1beta1Interface,IngressInterface
    96  //go:generate mockgen -package mocks -destination mocks/storagev1_mock.go k8s.io/client-go/kubernetes/typed/storage/v1 StorageV1Interface,StorageClassInterface
    97  
    98  // NewK8sClientFunc defines a function which returns a k8s client based on the supplied config.
    99  type NewK8sClientFunc func(c *rest.Config) (kubernetes.Interface, apiextensionsclientset.Interface, error)
   100  
   101  // NewK8sWatcherFunc defines a function which returns a k8s watcher based on the supplied config.
   102  type NewK8sWatcherFunc func(wi watch.Interface, name string, clock jujuclock.Clock) (*kubernetesWatcher, error)
   103  
   104  // NewK8sBroker returns a kubernetes client for the specified k8s cluster.
   105  func NewK8sBroker(
   106  	k8sRestConfig *rest.Config,
   107  	cfg *config.Config,
   108  	newClient NewK8sClientFunc,
   109  	newWatcher NewK8sWatcherFunc,
   110  	clock jujuclock.Clock,
   111  ) (caas.Broker, error) {
   112  	k8sClient, apiextensionsClient, err := newClient(k8sRestConfig)
   113  	if err != nil {
   114  		return nil, errors.Trace(err)
   115  	}
   116  	newCfg, err := providerInstance.newConfig(cfg)
   117  	if err != nil {
   118  		return nil, errors.Trace(err)
   119  	}
   120  	return &kubernetesClient{
   121  		clock:               clock,
   122  		Interface:           k8sClient,
   123  		apiextensionsClient: apiextensionsClient,
   124  		namespace:           newCfg.Name(),
   125  		envCfg:              newCfg,
   126  		modelUUID:           newCfg.UUID(),
   127  		newWatcher:          newWatcher,
   128  	}, nil
   129  }
   130  
   131  // Config returns environ config.
   132  func (k *kubernetesClient) Config() *config.Config {
   133  	k.lock.Lock()
   134  	defer k.lock.Unlock()
   135  	cfg := k.envCfg
   136  	return cfg
   137  }
   138  
   139  // SetConfig is specified in the Environ interface.
   140  func (k *kubernetesClient) SetConfig(cfg *config.Config) error {
   141  	k.lock.Lock()
   142  	defer k.lock.Unlock()
   143  	newCfg, err := providerInstance.newConfig(cfg)
   144  	if err != nil {
   145  		return errors.Trace(err)
   146  	}
   147  	k.envCfg = newCfg
   148  	return nil
   149  }
   150  
   151  // PrepareForBootstrap prepares for bootstraping a controller.
   152  func (k *kubernetesClient) PrepareForBootstrap(ctx environs.BootstrapContext) error {
   153  	return nil
   154  }
   155  
   156  const regionLabelName = "failure-domain.beta.kubernetes.io/region"
   157  
   158  // ListHostCloudRegions lists all the cloud regions that this cluster has worker nodes/instances running in.
   159  func (k *kubernetesClient) ListHostCloudRegions() (set.Strings, error) {
   160  	// we only check 5 worker nodes as of now just run in the one region and
   161  	// we are just looking for a running worker to sniff its region.
   162  	nodes, err := k.CoreV1().Nodes().List(v1.ListOptions{Limit: 5})
   163  	if err != nil {
   164  		return nil, errors.Annotate(err, "listing nodes")
   165  	}
   166  	result := set.NewStrings()
   167  	for _, n := range nodes.Items {
   168  		var cloudRegion, v string
   169  		var ok bool
   170  		if v = getCloudProviderFromNodeMeta(n); v == "" {
   171  			continue
   172  		}
   173  		cloudRegion += v
   174  		if v, ok = n.Labels[regionLabelName]; !ok || v == "" {
   175  			continue
   176  		}
   177  		cloudRegion += "/" + v
   178  		result.Add(cloudRegion)
   179  	}
   180  	return result, nil
   181  }
   182  
   183  // Bootstrap deploys controller with mongoDB together into k8s cluster.
   184  func (k *kubernetesClient) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   185  	const (
   186  		// TODO(caas): how to get these from oci path.
   187  		Series = "bionic"
   188  		Arch   = arch.AMD64
   189  	)
   190  
   191  	finalizer := func(ctx environs.BootstrapContext, pcfg *podcfg.ControllerPodConfig, opts environs.BootstrapDialOpts) error {
   192  		envConfig := k.Config()
   193  		if err := podcfg.FinishControllerPodConfig(pcfg, envConfig); err != nil {
   194  			return errors.Trace(err)
   195  		}
   196  
   197  		if err := pcfg.VerifyConfig(); err != nil {
   198  			return errors.Trace(err)
   199  		}
   200  
   201  		// prepare bootstrapParamsFile
   202  		bootstrapParamsFileContent, err := pcfg.Bootstrap.StateInitializationParams.Marshal()
   203  		if err != nil {
   204  			return errors.Trace(err)
   205  		}
   206  		logger.Debugf("bootstrapParams file content: \n%s", string(bootstrapParamsFileContent))
   207  
   208  		// TODO(caas): we'll need a different tag type other than machine tag.
   209  		machineTag := names.NewMachineTag(pcfg.MachineId)
   210  		acfg, err := pcfg.AgentConfig(machineTag)
   211  		if err != nil {
   212  			return errors.Trace(err)
   213  		}
   214  		agentConfigFileContent, err := acfg.Render()
   215  		if err != nil {
   216  			return errors.Trace(err)
   217  		}
   218  		logger.Debugf("agentConfig file content: \n%s", string(agentConfigFileContent))
   219  
   220  		// TODO(caas): prepare
   221  		// agent.conf,
   222  		// bootstrap-params,
   223  		// server.pem,
   224  		// system-identity,
   225  		// shared-secret, then generate configmap/secret.
   226  		// Lastly, create StatefulSet for controller.
   227  		return nil
   228  	}
   229  	return &environs.BootstrapResult{
   230  		Arch:                   Arch,
   231  		Series:                 Series,
   232  		CaasBootstrapFinalizer: finalizer,
   233  	}, nil
   234  }
   235  
   236  // DestroyController implements the Environ interface.
   237  func (k *kubernetesClient) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error {
   238  	// TODO(caas): destroy controller and all models
   239  	logger.Warningf("DestroyController is not supported yet on CAAS.")
   240  	return nil
   241  }
   242  
   243  // Provider is part of the Broker interface.
   244  func (*kubernetesClient) Provider() caas.ContainerEnvironProvider {
   245  	return providerInstance
   246  }
   247  
   248  // Destroy is part of the Broker interface.
   249  func (k *kubernetesClient) Destroy(callbacks context.ProviderCallContext) error {
   250  	watcher, err := k.WatchNamespace()
   251  	if err != nil {
   252  		return errors.Trace(err)
   253  	}
   254  	defer watcher.Kill()
   255  
   256  	if err := k.deleteNamespace(); err != nil {
   257  		return errors.Annotate(err, "deleting model namespace")
   258  	}
   259  
   260  	// Delete any storage classes created as part of this model.
   261  	// Storage classes live outside the namespace so need to be deleted separately.
   262  	modelSelector := fmt.Sprintf("%s==%s", labelModel, k.namespace)
   263  	err = k.StorageV1().StorageClasses().DeleteCollection(&v1.DeleteOptions{
   264  		PropagationPolicy: &defaultPropagationPolicy,
   265  	}, v1.ListOptions{
   266  		LabelSelector: modelSelector,
   267  	})
   268  	if err != nil && !k8serrors.IsNotFound(err) {
   269  		return errors.Annotate(err, "deleting model storage classes")
   270  	}
   271  	for {
   272  		select {
   273  		case <-callbacks.Dying():
   274  			return nil
   275  		case <-watcher.Changes():
   276  			// ensure namespace has been deleted - notfound error expected.
   277  			_, err := k.GetNamespace("")
   278  			if errors.IsNotFound(err) {
   279  				// namespace ha been deleted.
   280  				return nil
   281  			}
   282  			if err != nil {
   283  				return errors.Trace(err)
   284  			}
   285  			logger.Debugf("namespace %q is still been terminating", k.namespace)
   286  		}
   287  	}
   288  }
   289  
   290  // Namespaces returns names of the namespaces on the cluster.
   291  func (k *kubernetesClient) Namespaces() ([]string, error) {
   292  	namespaces := k.CoreV1().Namespaces()
   293  	ns, err := namespaces.List(v1.ListOptions{IncludeUninitialized: true})
   294  	if err != nil {
   295  		return nil, errors.Annotate(err, "listing namespaces")
   296  	}
   297  	result := make([]string, len(ns.Items))
   298  	for i, n := range ns.Items {
   299  		result[i] = n.Name
   300  	}
   301  	return result, nil
   302  }
   303  
   304  // GetNamespace returns the namespace for the specified name or current namespace.
   305  func (k *kubernetesClient) GetNamespace(name string) (*core.Namespace, error) {
   306  	if name == "" {
   307  		name = k.namespace
   308  	}
   309  	ns, err := k.CoreV1().Namespaces().Get(name, v1.GetOptions{IncludeUninitialized: true})
   310  	if k8serrors.IsNotFound(err) {
   311  		return nil, errors.NotFoundf("namespace %q", name)
   312  	}
   313  	if err != nil {
   314  		return nil, errors.Annotate(err, "getting namespaces")
   315  	}
   316  	return ns, nil
   317  }
   318  
   319  // EnsureNamespace ensures this broker's namespace is created.
   320  func (k *kubernetesClient) EnsureNamespace() error {
   321  	ns := &core.Namespace{ObjectMeta: v1.ObjectMeta{Name: k.namespace}}
   322  	namespaces := k.CoreV1().Namespaces()
   323  	_, err := namespaces.Update(ns)
   324  	if k8serrors.IsNotFound(err) {
   325  		_, err = namespaces.Create(ns)
   326  	}
   327  	return errors.Trace(err)
   328  }
   329  
   330  func (k *kubernetesClient) deleteNamespace() error {
   331  	// deleteNamespace is used as a means to implement Destroy().
   332  	// All model resources are provisioned in the namespace;
   333  	// deleting the namespace will also delete those resources.
   334  	err := k.CoreV1().Namespaces().Delete(k.namespace, &v1.DeleteOptions{
   335  		PropagationPolicy: &defaultPropagationPolicy,
   336  	})
   337  	if k8serrors.IsNotFound(err) {
   338  		return nil
   339  	}
   340  	return errors.Trace(err)
   341  }
   342  
   343  // WatchNamespace returns a watcher which notifies when there
   344  // are changes to current namespace.
   345  func (k *kubernetesClient) WatchNamespace() (watcher.NotifyWatcher, error) {
   346  	w, err := k.CoreV1().Namespaces().Watch(
   347  		v1.ListOptions{
   348  			FieldSelector:        fields.OneTermEqualSelector("metadata.name", k.namespace).String(),
   349  			IncludeUninitialized: true,
   350  		},
   351  	)
   352  	if err != nil {
   353  		return nil, errors.Trace(err)
   354  	}
   355  	return k.newWatcher(w, k.namespace, k.clock)
   356  }
   357  
   358  // EnsureSecret ensures a secret exists for use with retrieving images from private registries
   359  func (k *kubernetesClient) ensureSecret(imageSecretName, appName string, imageDetails *caas.ImageDetails, resourceTags map[string]string) error {
   360  	if imageDetails.Password == "" {
   361  		return errors.New("attempting to create a secret with no password")
   362  	}
   363  	secretData, err := createDockerConfigJSON(imageDetails)
   364  	if err != nil {
   365  		return errors.Trace(err)
   366  	}
   367  	secrets := k.CoreV1().Secrets(k.namespace)
   368  
   369  	newSecret := &core.Secret{
   370  		ObjectMeta: v1.ObjectMeta{
   371  			Name:      imageSecretName,
   372  			Namespace: k.namespace,
   373  			Labels:    resourceTags},
   374  		Type: core.SecretTypeDockerConfigJson,
   375  		Data: map[string][]byte{
   376  			core.DockerConfigJsonKey: secretData,
   377  		},
   378  	}
   379  
   380  	_, err = secrets.Update(newSecret)
   381  	if k8serrors.IsNotFound(err) {
   382  		_, err = secrets.Create(newSecret)
   383  	}
   384  	return errors.Trace(err)
   385  }
   386  
   387  func (k *kubernetesClient) deleteSecret(imageSecretName string) error {
   388  	secrets := k.CoreV1().Secrets(k.namespace)
   389  	err := secrets.Delete(imageSecretName, &v1.DeleteOptions{
   390  		PropagationPolicy: &defaultPropagationPolicy,
   391  	})
   392  	if k8serrors.IsNotFound(err) {
   393  		return nil
   394  	}
   395  	return errors.Trace(err)
   396  }
   397  
   398  // OperatorExists returns true if the operator for the specified
   399  // application exists.
   400  func (k *kubernetesClient) OperatorExists(appName string) (bool, error) {
   401  	statefulsets := k.AppsV1().StatefulSets(k.namespace)
   402  	_, err := statefulsets.Get(k.operatorName(appName), v1.GetOptions{IncludeUninitialized: true})
   403  	if k8serrors.IsNotFound(err) {
   404  		return false, nil
   405  	}
   406  	if err != nil {
   407  		return false, errors.Trace(err)
   408  	}
   409  	return true, nil
   410  }
   411  
   412  // EnsureOperator creates or updates an operator pod with the given application
   413  // name, agent path, and operator config.
   414  func (k *kubernetesClient) EnsureOperator(appName, agentPath string, config *caas.OperatorConfig) error {
   415  	logger.Debugf("creating/updating %s operator", appName)
   416  
   417  	// TODO(caas) - this is a stop gap until we implement a CAAS model manager worker
   418  	// First up, ensure the namespace eis there if not already created.
   419  	if err := k.EnsureNamespace(); err != nil {
   420  		return errors.Annotatef(err, "ensuring operator namespace %v", k.namespace)
   421  	}
   422  
   423  	operatorName := k.operatorName(appName)
   424  	// TODO(caas) use secrets for storing agent password?
   425  	if config.AgentConf == nil {
   426  		// We expect that the config map already exists,
   427  		// so make sure it does.
   428  		configMaps := k.CoreV1().ConfigMaps(k.namespace)
   429  		_, err := configMaps.Get(operatorConfigMapName(operatorName), v1.GetOptions{IncludeUninitialized: true})
   430  		if err != nil {
   431  			return errors.Annotatef(err, "config map for %q should already exist", appName)
   432  		}
   433  	} else {
   434  		if err := k.ensureConfigMap(operatorConfigMap(appName, operatorName, config)); err != nil {
   435  			return errors.Annotate(err, "creating or updating ConfigMap")
   436  		}
   437  	}
   438  
   439  	storageTags := make(map[string]string)
   440  	for k, v := range config.CharmStorage.ResourceTags {
   441  		storageTags[k] = v
   442  	}
   443  	storageTags[labelOperator] = appName
   444  
   445  	tags := make(map[string]string)
   446  	for k, v := range config.ResourceTags {
   447  		tags[k] = v
   448  	}
   449  	tags[labelOperator] = appName
   450  
   451  	// Set up the parameters for creating charm storage.
   452  	operatorVolumeClaim := "charm"
   453  	if isLegacyName(operatorName) {
   454  		operatorVolumeClaim = fmt.Sprintf("%v-operator-volume", appName)
   455  	}
   456  
   457  	params := volumeParams{
   458  		storageConfig:       &storageConfig{existingStorageClass: defaultOperatorStorageClassName},
   459  		storageLabels:       caas.OperatorStorageClassLabels(appName, k.namespace),
   460  		pvcName:             operatorVolumeClaim,
   461  		requestedVolumeSize: fmt.Sprintf("%dMi", config.CharmStorage.Size),
   462  	}
   463  	if config.CharmStorage.Provider != K8s_ProviderType {
   464  		return errors.Errorf("expected charm storage provider %q, got %q", K8s_ProviderType, config.CharmStorage.Provider)
   465  	}
   466  	if storageLabel, ok := config.CharmStorage.Attributes[storageLabel]; ok {
   467  		params.storageLabels = append([]string{fmt.Sprintf("%v", storageLabel)}, params.storageLabels...)
   468  	}
   469  	var err error
   470  	params.storageConfig, err = newStorageConfig(config.CharmStorage.Attributes, defaultOperatorStorageClassName)
   471  	if err != nil {
   472  		return errors.Annotatef(err, "invalid storage configuration for %v operator", appName)
   473  	}
   474  	// We want operator storage to be deleted when the operator goes away.
   475  	params.storageConfig.reclaimPolicy = core.PersistentVolumeReclaimDelete
   476  	logger.Debugf("operator storage config %#v", *params.storageConfig)
   477  
   478  	// Attempt to get a persistent volume to store charm state etc.
   479  	pvcSpec, err := k.maybeGetVolumeClaimSpec(params)
   480  	if err != nil {
   481  		return errors.Annotate(err, "finding operator volume claim")
   482  	}
   483  	pvc := &core.PersistentVolumeClaim{
   484  		ObjectMeta: v1.ObjectMeta{
   485  			Name:   params.pvcName,
   486  			Labels: storageTags},
   487  		Spec: *pvcSpec,
   488  	}
   489  	pod := operatorPod(operatorName, appName, agentPath, config.OperatorImagePath, config.Version.String(), tags)
   490  	// Take a copy for use with statefulset.
   491  	podWithoutStorage := pod
   492  
   493  	numPods := int32(1)
   494  	logger.Debugf("using persistent volume claim for operator %s: %+v", appName, pvc)
   495  	statefulset := &apps.StatefulSet{
   496  		ObjectMeta: v1.ObjectMeta{
   497  			Name:   operatorName,
   498  			Labels: pod.Labels},
   499  		Spec: apps.StatefulSetSpec{
   500  			Replicas: &numPods,
   501  			Selector: &v1.LabelSelector{
   502  				MatchLabels: map[string]string{labelOperator: appName},
   503  			},
   504  			Template: core.PodTemplateSpec{
   505  				ObjectMeta: v1.ObjectMeta{
   506  					Labels: pod.Labels,
   507  				},
   508  			},
   509  			PodManagementPolicy:  apps.ParallelPodManagement,
   510  			VolumeClaimTemplates: []core.PersistentVolumeClaim{*pvc},
   511  		},
   512  	}
   513  	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, core.VolumeMount{
   514  		Name:      pvc.Name,
   515  		MountPath: agent.BaseDir(agentPath),
   516  	})
   517  
   518  	statefulset.Spec.Template.Spec = pod.Spec
   519  	err = k.ensureStatefulSet(statefulset, podWithoutStorage.Spec)
   520  	return errors.Annotatef(err, "creating or updating %v operator StatefulSet", appName)
   521  }
   522  
   523  func (k *kubernetesClient) GetStorageClassName(labels ...string) (string, error) {
   524  	sc, err := k.maybeGetStorageClass(labels...)
   525  	if err != nil {
   526  		return "", errors.Trace(err)
   527  	}
   528  	return sc.Name, nil
   529  }
   530  
   531  // maybeGetStorageClass looks for a storage class to use when creating
   532  // a persistent volume, using the specified name (if supplied), or a class
   533  // matching the specified labels.
   534  func (k *kubernetesClient) maybeGetStorageClass(labels ...string) (*k8sstorage.StorageClass, error) {
   535  	// First try looking for a storage class with a Juju label.
   536  	selector := fmt.Sprintf("%v in (%v)", labelStorage, strings.Join(labels, ", "))
   537  	modelTerm := fmt.Sprintf("%s==%s", labelModel, k.namespace)
   538  	modelSelector := selector + "," + modelTerm
   539  
   540  	// Attempt to get a storage class tied to this model.
   541  	storageClasses, err := k.StorageV1().StorageClasses().List(v1.ListOptions{
   542  		LabelSelector: modelSelector,
   543  	})
   544  	if err != nil {
   545  		return nil, errors.Annotatef(err, "looking for existing storage class with selector %q", modelSelector)
   546  	}
   547  
   548  	// If no storage classes tied to this model, look for a non-model specific
   549  	// storage class with the relevant labels.
   550  	if len(storageClasses.Items) == 0 {
   551  		storageClasses, err = k.StorageV1().StorageClasses().List(v1.ListOptions{
   552  			LabelSelector: selector,
   553  		})
   554  		if err != nil {
   555  			return nil, errors.Annotatef(err, "looking for existing storage class with selector %q", modelSelector)
   556  		}
   557  	}
   558  	logger.Debugf("available storage classes: %v", storageClasses.Items)
   559  	// For now, pick the first matching storage class.
   560  	if len(storageClasses.Items) > 0 {
   561  		return &storageClasses.Items[0], nil
   562  	}
   563  
   564  	// Second look for the cluster default storage class, if defined.
   565  	storageClasses, err = k.StorageV1().StorageClasses().List(v1.ListOptions{})
   566  	if err != nil {
   567  		return nil, errors.Annotate(err, "listing storage classes")
   568  	}
   569  	for _, sc := range storageClasses.Items {
   570  		if v, ok := sc.Annotations["storageclass.kubernetes.io/is-default-class"]; ok && v != "false" {
   571  			logger.Debugf("using default storage class: %v", sc.Name)
   572  			return &sc, nil
   573  		}
   574  	}
   575  	return nil, errors.NotFoundf("storage class for any %q", labels)
   576  }
   577  
   578  type volumeParams struct {
   579  	storageLabels       []string
   580  	storageConfig       *storageConfig
   581  	pvcName             string
   582  	requestedVolumeSize string
   583  	accessMode          core.PersistentVolumeAccessMode
   584  }
   585  
   586  // maybeGetVolumeClaimSpec returns a persistent volume claim spec for the given
   587  // parameters. If no suitable storage class is available, return a NotFound error.
   588  func (k *kubernetesClient) maybeGetVolumeClaimSpec(params volumeParams) (*core.PersistentVolumeClaimSpec, error) {
   589  	storageClassName := params.storageConfig.storageClass
   590  	existingStorageClassName := params.storageConfig.existingStorageClass
   591  	haveStorageClass := false
   592  	// If no specific storage class has been specified but there's a default
   593  	// fallback one, try and look for that first.
   594  	if storageClassName == "" && existingStorageClassName != "" {
   595  		sc, err := k.getStorageClass(existingStorageClassName)
   596  		if err != nil && !k8serrors.IsNotFound(err) {
   597  			return nil, errors.Annotatef(err, "looking for existing storage class %q", existingStorageClassName)
   598  		}
   599  		if err == nil {
   600  			haveStorageClass = true
   601  			storageClassName = sc.Name
   602  		}
   603  	}
   604  	// If no storage class has been found or asked for,
   605  	// look for one by matching labels.
   606  	if storageClassName == "" && !haveStorageClass {
   607  		sc, err := k.maybeGetStorageClass(params.storageLabels...)
   608  		if err != nil && !errors.IsNotFound(err) {
   609  			return nil, errors.Trace(err)
   610  		}
   611  		if err == nil {
   612  			haveStorageClass = true
   613  			storageClassName = sc.Name
   614  		}
   615  	}
   616  	// If a specific storage class has been requested, make sure it exists.
   617  	if storageClassName != "" && !haveStorageClass {
   618  		params.storageConfig.storageClass = storageClassName
   619  		sc, err := k.ensureStorageClass(params.storageConfig)
   620  		if err != nil && !errors.IsNotFound(err) {
   621  			return nil, errors.Trace(err)
   622  		}
   623  		if err == nil {
   624  			haveStorageClass = true
   625  			storageClassName = sc.Name
   626  		}
   627  	}
   628  	if !haveStorageClass {
   629  		return nil, errors.NewNotFound(nil, fmt.Sprintf(
   630  			"cannot create persistent volume as no storage class matching %q exists and no default storage class is defined",
   631  			params.storageLabels))
   632  	}
   633  	accessMode := params.accessMode
   634  	if accessMode == "" {
   635  		accessMode = core.ReadWriteOnce
   636  	}
   637  	fsSize, err := resource.ParseQuantity(params.requestedVolumeSize)
   638  	if err != nil {
   639  		return nil, errors.Annotatef(err, "invalid volume size %v", params.requestedVolumeSize)
   640  	}
   641  	return &core.PersistentVolumeClaimSpec{
   642  		StorageClassName: &storageClassName,
   643  		Resources: core.ResourceRequirements{
   644  			Requests: core.ResourceList{
   645  				core.ResourceStorage: fsSize,
   646  			},
   647  		},
   648  		AccessModes: []core.PersistentVolumeAccessMode{accessMode},
   649  	}, nil
   650  }
   651  
   652  // getStorageClass returns a named storage class, first looking for
   653  // one which is qualified by the current namespace if it's available.
   654  func (k *kubernetesClient) getStorageClass(name string) (*k8sstorage.StorageClass, error) {
   655  	storageClasses := k.StorageV1().StorageClasses()
   656  	qualifiedName := qualifiedStorageClassName(k.namespace, name)
   657  	sc, err := storageClasses.Get(qualifiedName, v1.GetOptions{})
   658  	if err == nil {
   659  		return sc, nil
   660  	}
   661  	if !k8serrors.IsNotFound(err) {
   662  		return nil, errors.Trace(err)
   663  	}
   664  	return storageClasses.Get(name, v1.GetOptions{})
   665  }
   666  
   667  func (k *kubernetesClient) ensureStorageClass(cfg *storageConfig) (*k8sstorage.StorageClass, error) {
   668  	// First see if the named storage class exists.
   669  	sc, err := k.getStorageClass(cfg.storageClass)
   670  	if err == nil {
   671  		return sc, nil
   672  	}
   673  	if !k8serrors.IsNotFound(err) {
   674  		return nil, errors.Annotatef(err, "getting storage class %q", cfg.storageClass)
   675  	}
   676  	// If it's not found but there's no provisioner specified, we can't
   677  	// create it so just return not found.
   678  	if err != nil && cfg.storageProvisioner == "" {
   679  		return nil, errors.NewNotFound(nil,
   680  			fmt.Sprintf("storage class %q doesn't exist, but no storage provisioner has been specified",
   681  				cfg.storageClass))
   682  	}
   683  
   684  	// Create the storage class with the specified provisioner.
   685  	storageClasses := k.StorageV1().StorageClasses()
   686  	sc, err = storageClasses.Create(&k8sstorage.StorageClass{
   687  		ObjectMeta: v1.ObjectMeta{
   688  			Name:   qualifiedStorageClassName(k.namespace, cfg.storageClass),
   689  			Labels: map[string]string{labelModel: k.namespace},
   690  		},
   691  		Provisioner:   cfg.storageProvisioner,
   692  		ReclaimPolicy: &cfg.reclaimPolicy,
   693  		Parameters:    cfg.parameters,
   694  	})
   695  	return sc, errors.Annotatef(err, "creating storage class %q", cfg.storageClass)
   696  }
   697  
   698  // DeleteOperator deletes the specified operator.
   699  func (k *kubernetesClient) DeleteOperator(appName string) (err error) {
   700  	logger.Debugf("deleting %s operator", appName)
   701  
   702  	operatorName := k.operatorName(appName)
   703  	legacy := isLegacyName(operatorName)
   704  
   705  	// First delete the config map(s).
   706  	configMaps := k.CoreV1().ConfigMaps(k.namespace)
   707  	configMapName := operatorConfigMapName(operatorName)
   708  	err = configMaps.Delete(configMapName, &v1.DeleteOptions{
   709  		PropagationPolicy: &defaultPropagationPolicy,
   710  	})
   711  	if err != nil && !k8serrors.IsNotFound(err) {
   712  		return nil
   713  	}
   714  
   715  	// Delete artefacts created by k8s itself.
   716  	configMapName = appName + "-configurations-config"
   717  	if legacy {
   718  		configMapName = "juju-" + configMapName
   719  	}
   720  	err = configMaps.Delete(configMapName, &v1.DeleteOptions{
   721  		PropagationPolicy: &defaultPropagationPolicy,
   722  	})
   723  	if err != nil && !k8serrors.IsNotFound(err) {
   724  		return nil
   725  	}
   726  
   727  	// Finally the operator itself.
   728  	if err := k.deleteStatefulSet(operatorName); err != nil {
   729  		return errors.Trace(err)
   730  	}
   731  	pods := k.CoreV1().Pods(k.namespace)
   732  	podsList, err := pods.List(v1.ListOptions{
   733  		LabelSelector: operatorSelector(appName),
   734  	})
   735  	if err != nil {
   736  		return errors.Trace(err)
   737  	}
   738  
   739  	deploymentName := appName
   740  	if legacy {
   741  		deploymentName = "juju-" + appName
   742  	}
   743  	pvs := k.CoreV1().PersistentVolumes()
   744  	for _, p := range podsList.Items {
   745  		// Delete secrets.
   746  		for _, c := range p.Spec.Containers {
   747  			secretName := appSecretName(deploymentName, c.Name)
   748  			if err := k.deleteSecret(secretName); err != nil {
   749  				return errors.Annotatef(err, "deleting %s secret for container %s", appName, c.Name)
   750  			}
   751  		}
   752  		// Delete operator storage volumes.
   753  		volumeNames, err := k.deleteVolumeClaims(appName, &p)
   754  		if err != nil {
   755  			return errors.Trace(err)
   756  		}
   757  		// Just in case the volume reclaim policy is retain, we force deletion
   758  		// for operators as the volume is an inseparable part of the operator.
   759  		for _, volName := range volumeNames {
   760  			err = pvs.Delete(volName, &v1.DeleteOptions{
   761  				PropagationPolicy: &defaultPropagationPolicy,
   762  			})
   763  			if err != nil && !k8serrors.IsNotFound(err) {
   764  				return errors.Annotatef(err, "deleting operator persistent volume %v for %v",
   765  					volName, appName)
   766  			}
   767  		}
   768  	}
   769  	return errors.Trace(k.deleteDeployment(operatorName))
   770  }
   771  
   772  // Service returns the service for the specified application.
   773  func (k *kubernetesClient) Service(appName string) (*caas.Service, error) {
   774  	services := k.CoreV1().Services(k.namespace)
   775  	servicesList, err := services.List(v1.ListOptions{
   776  		LabelSelector: applicationSelector(appName),
   777  	})
   778  	if err != nil {
   779  		return nil, errors.Trace(err)
   780  	}
   781  	if len(servicesList.Items) == 0 {
   782  		return nil, errors.NotFoundf("service for %q", appName)
   783  	}
   784  	service := servicesList.Items[0]
   785  	result := caas.Service{
   786  		Id: string(service.UID),
   787  	}
   788  	if service.Spec.ClusterIP != "" {
   789  		result.Addresses = append(result.Addresses, network.Address{
   790  			Value: service.Spec.ClusterIP,
   791  			Type:  network.DeriveAddressType(service.Spec.ClusterIP),
   792  			Scope: network.ScopeCloudLocal,
   793  		})
   794  	}
   795  	if service.Spec.LoadBalancerIP != "" {
   796  		result.Addresses = append(result.Addresses, network.Address{
   797  			Value: service.Spec.LoadBalancerIP,
   798  			Type:  network.DeriveAddressType(service.Spec.LoadBalancerIP),
   799  			Scope: network.ScopePublic,
   800  		})
   801  	}
   802  	for _, addr := range service.Spec.ExternalIPs {
   803  		result.Addresses = append(result.Addresses, network.Address{
   804  			Value: addr,
   805  			Type:  network.DeriveAddressType(addr),
   806  			Scope: network.ScopePublic,
   807  		})
   808  	}
   809  	return &result, nil
   810  }
   811  
   812  // DeleteService deletes the specified service.
   813  func (k *kubernetesClient) DeleteService(appName string) (err error) {
   814  	logger.Debugf("deleting application %s", appName)
   815  
   816  	deploymentName := k.deploymentName(appName)
   817  	if err := k.deleteService(deploymentName); err != nil {
   818  		return errors.Trace(err)
   819  	}
   820  	if err := k.deleteStatefulSet(deploymentName); err != nil {
   821  		return errors.Trace(err)
   822  	}
   823  	if err := k.deleteDeployment(deploymentName); err != nil {
   824  		return errors.Trace(err)
   825  	}
   826  	pods := k.CoreV1().Pods(k.namespace)
   827  	podsList, err := pods.List(v1.ListOptions{
   828  		LabelSelector: applicationSelector(appName),
   829  	})
   830  	if err != nil {
   831  		return errors.Trace(err)
   832  	}
   833  	for _, p := range podsList.Items {
   834  		if _, err := k.deleteVolumeClaims(appName, &p); err != nil {
   835  			return errors.Trace(err)
   836  		}
   837  	}
   838  	secrets := k.CoreV1().Secrets(k.namespace)
   839  	secretList, err := secrets.List(v1.ListOptions{
   840  		LabelSelector: applicationSelector(appName),
   841  	})
   842  	if err != nil {
   843  		return errors.Trace(err)
   844  	}
   845  	for _, s := range secretList.Items {
   846  		if err := k.deleteSecret(s.Name); err != nil {
   847  			return errors.Trace(err)
   848  		}
   849  	}
   850  	return nil
   851  }
   852  
   853  // EnsureCustomResourceDefinition creates or updates a custom resource definition resource.
   854  func (k *kubernetesClient) EnsureCustomResourceDefinition(appName string, podSpec *caas.PodSpec) error {
   855  	for _, t := range podSpec.CustomResourceDefinitions {
   856  		crd, err := k.ensureCustomResourceDefinitionTemplate(&t)
   857  		if err != nil {
   858  			return errors.Annotate(err, fmt.Sprintf("ensure custom resource definition %q", t.Kind))
   859  		}
   860  		logger.Debugf("ensured custom resource definition %q", crd.ObjectMeta.Name)
   861  	}
   862  	return nil
   863  }
   864  
   865  func (k *kubernetesClient) ensureCustomResourceDefinitionTemplate(t *caas.CustomResourceDefinition) (
   866  	crd *apiextensionsv1beta1.CustomResourceDefinition, err error) {
   867  	singularName := strings.ToLower(t.Kind)
   868  	pluralName := fmt.Sprintf("%ss", singularName)
   869  	crdFullName := fmt.Sprintf("%s.%s", pluralName, t.Group)
   870  	crdIn := &apiextensionsv1beta1.CustomResourceDefinition{
   871  		ObjectMeta: v1.ObjectMeta{
   872  			Name:      crdFullName,
   873  			Namespace: k.namespace,
   874  		},
   875  		Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
   876  			Group:   t.Group,
   877  			Version: t.Version,
   878  			Scope:   apiextensionsv1beta1.ResourceScope(t.Scope),
   879  			Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
   880  				Plural:   pluralName,
   881  				Kind:     t.Kind,
   882  				Singular: singularName,
   883  			},
   884  			Validation: &apiextensionsv1beta1.CustomResourceValidation{
   885  				OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
   886  					Properties: t.Validation.Properties,
   887  				},
   888  			},
   889  		},
   890  	}
   891  	apiextensionsV1beta1 := k.apiextensionsClient.ApiextensionsV1beta1()
   892  	logger.Debugf("creating crd %#v", crdIn)
   893  	crd, err = apiextensionsV1beta1.CustomResourceDefinitions().Create(crdIn)
   894  	if k8serrors.IsAlreadyExists(err) {
   895  		crd, err = apiextensionsV1beta1.CustomResourceDefinitions().Get(crdFullName, v1.GetOptions{})
   896  		resourceVersion := crd.ObjectMeta.GetResourceVersion()
   897  		crdIn.ObjectMeta.SetResourceVersion(resourceVersion)
   898  		logger.Debugf("existing crd with resource version %q found, so update it %#v", resourceVersion, crdIn)
   899  		crd, err = apiextensionsV1beta1.CustomResourceDefinitions().Update(crdIn)
   900  	}
   901  	return
   902  }
   903  
   904  // EnsureService creates or updates a service for pods with the given params.
   905  func (k *kubernetesClient) EnsureService(
   906  	appName string, statusCallback caas.StatusCallbackFunc, params *caas.ServiceParams, numUnits int, config application.ConfigAttributes,
   907  ) (err error) {
   908  	defer func() {
   909  		if err != nil {
   910  			statusCallback(appName, status.Error, err.Error(), nil)
   911  		}
   912  	}()
   913  
   914  	logger.Debugf("creating/updating application %s", appName)
   915  	deploymentName := k.deploymentName(appName)
   916  
   917  	if numUnits < 0 {
   918  		return errors.Errorf("number of units must be >= 0")
   919  	}
   920  	if numUnits == 0 {
   921  		return k.deleteAllPods(appName, deploymentName)
   922  	}
   923  	if params == nil || params.PodSpec == nil {
   924  		return errors.Errorf("missing pod spec")
   925  	}
   926  	if params.PodSpec.OmitServiceFrontend && len(params.Filesystems) == 0 {
   927  		return errors.Errorf("kubernetes service is required when using storage")
   928  	}
   929  
   930  	var cleanups []func()
   931  	defer func() {
   932  		if err == nil {
   933  			return
   934  		}
   935  		for _, f := range cleanups {
   936  			f()
   937  		}
   938  	}()
   939  
   940  	unitSpec, err := makeUnitSpec(appName, deploymentName, params.PodSpec)
   941  	if err != nil {
   942  		return errors.Annotatef(err, "parsing unit spec for %s", appName)
   943  	}
   944  	if len(params.Devices) > 0 {
   945  		if err = k.configureDevices(unitSpec, params.Devices); err != nil {
   946  			return errors.Annotatef(err, "configuring devices for %s", appName)
   947  		}
   948  	}
   949  	if mem := params.Constraints.Mem; mem != nil {
   950  		if err = k.configureConstraint(unitSpec, "memory", fmt.Sprintf("%dMi", *mem)); err != nil {
   951  			return errors.Annotatef(err, "configuring memory constraint for %s", appName)
   952  		}
   953  	}
   954  	if cpu := params.Constraints.CpuPower; cpu != nil {
   955  		if err = k.configureConstraint(unitSpec, "cpu", fmt.Sprintf("%dm", *cpu)); err != nil {
   956  			return errors.Annotatef(err, "configuring cpu constraint for %s", appName)
   957  		}
   958  	}
   959  	if params.Placement != "" {
   960  		affinityLabels, err := keyvalues.Parse(strings.Split(params.Placement, ","), false)
   961  		if err != nil {
   962  			return errors.Annotatef(err, "invalid placement directive %q", params.Placement)
   963  		}
   964  		unitSpec.Pod.NodeSelector = affinityLabels
   965  	}
   966  
   967  	resourceTags := make(map[string]string)
   968  	for k, v := range params.ResourceTags {
   969  		resourceTags[k] = v
   970  	}
   971  	resourceTags[labelApplication] = appName
   972  	for _, c := range params.PodSpec.Containers {
   973  		if c.ImageDetails.Password == "" {
   974  			continue
   975  		}
   976  		imageSecretName := appSecretName(deploymentName, c.Name)
   977  		if err := k.ensureSecret(imageSecretName, appName, &c.ImageDetails, resourceTags); err != nil {
   978  			return errors.Annotatef(err, "creating secrets for container: %s", c.Name)
   979  		}
   980  		cleanups = append(cleanups, func() { k.deleteSecret(imageSecretName) })
   981  	}
   982  
   983  	// Add a deployment controller or stateful set configured to create the specified number of units/pods.
   984  	// Defensively check to see if a stateful set is already used.
   985  	useStatefulSet := len(params.Filesystems) > 0
   986  	if !useStatefulSet {
   987  		statefulsets := k.AppsV1().StatefulSets(k.namespace)
   988  		_, err := statefulsets.Get(deploymentName, v1.GetOptions{IncludeUninitialized: true})
   989  		if err != nil && !k8serrors.IsNotFound(err) {
   990  			return errors.Trace(err)
   991  		}
   992  		useStatefulSet = err == nil
   993  		if useStatefulSet {
   994  			logger.Debugf("no updated filesystems but already using stateful set for %v", appName)
   995  		}
   996  	}
   997  
   998  	numPods := int32(numUnits)
   999  	if useStatefulSet {
  1000  		if err := k.configureStatefulSet(appName, deploymentName, resourceTags, unitSpec, params.PodSpec.Containers, &numPods, params.Filesystems); err != nil {
  1001  			return errors.Annotate(err, "creating or updating StatefulSet")
  1002  		}
  1003  		cleanups = append(cleanups, func() { k.deleteDeployment(appName) })
  1004  	} else {
  1005  		if err := k.configureDeployment(appName, deploymentName, resourceTags, unitSpec, params.PodSpec.Containers, &numPods); err != nil {
  1006  			return errors.Annotate(err, "creating or updating DeploymentController")
  1007  		}
  1008  		cleanups = append(cleanups, func() { k.deleteDeployment(appName) })
  1009  	}
  1010  
  1011  	var ports []core.ContainerPort
  1012  	for _, c := range unitSpec.Pod.Containers {
  1013  		for _, p := range c.Ports {
  1014  			if p.ContainerPort == 0 {
  1015  				continue
  1016  			}
  1017  			ports = append(ports, p)
  1018  		}
  1019  	}
  1020  	if !params.PodSpec.OmitServiceFrontend {
  1021  		if err := k.configureService(appName, deploymentName, ports, resourceTags, config); err != nil {
  1022  			return errors.Annotatef(err, "creating or updating service for %v", appName)
  1023  		}
  1024  	}
  1025  	return nil
  1026  }
  1027  
  1028  func (k *kubernetesClient) deleteAllPods(appName, deploymentName string) error {
  1029  	zero := int32(0)
  1030  	statefulsets := k.AppsV1().StatefulSets(k.namespace)
  1031  	statefulSet, err := statefulsets.Get(deploymentName, v1.GetOptions{IncludeUninitialized: true})
  1032  	if err != nil && !k8serrors.IsNotFound(err) {
  1033  		return errors.Trace(err)
  1034  	}
  1035  	if err == nil {
  1036  		statefulSet.Spec.Replicas = &zero
  1037  		_, err = statefulsets.Update(statefulSet)
  1038  		return errors.Trace(err)
  1039  	}
  1040  
  1041  	deployments := k.AppsV1().Deployments(k.namespace)
  1042  	deployment, err := deployments.Get(deploymentName, v1.GetOptions{IncludeUninitialized: true})
  1043  	if k8serrors.IsNotFound(err) {
  1044  		return nil
  1045  	}
  1046  	if err != nil {
  1047  		return errors.Trace(err)
  1048  	}
  1049  	deployment.Spec.Replicas = &zero
  1050  	_, err = deployments.Update(deployment)
  1051  	return errors.Trace(err)
  1052  }
  1053  
  1054  func (k *kubernetesClient) configureStorage(
  1055  	podSpec *core.PodSpec, statefulSet *apps.StatefulSetSpec, appName string, legacy bool, filesystems []storage.KubernetesFilesystemParams,
  1056  ) error {
  1057  	baseDir, err := paths.StorageDir("kubernetes")
  1058  	if err != nil {
  1059  		return errors.Trace(err)
  1060  	}
  1061  	logger.Debugf("configuring pod filesystems: %+v", filesystems)
  1062  	for i, fs := range filesystems {
  1063  		if fs.Provider != K8s_ProviderType {
  1064  			return errors.Errorf("invalid storage provider type %q for %v", fs.Provider, fs.StorageName)
  1065  		}
  1066  		var mountPath string
  1067  		if fs.Attachment != nil {
  1068  			mountPath = fs.Attachment.Path
  1069  		}
  1070  		if mountPath == "" {
  1071  			mountPath = fmt.Sprintf("%s/fs/%s/%s/%d", baseDir, appName, fs.StorageName, i)
  1072  		}
  1073  		pvcNamePrefix := fmt.Sprintf("%s-%d", fs.StorageName, i)
  1074  		if legacy {
  1075  			pvcNamePrefix = "juju-" + pvcNamePrefix
  1076  		}
  1077  		params := volumeParams{
  1078  			storageLabels:       caas.UnitStorageClassLabels(appName, k.namespace),
  1079  			pvcName:             pvcNamePrefix,
  1080  			requestedVolumeSize: fmt.Sprintf("%dMi", fs.Size),
  1081  		}
  1082  		if storageLabel, ok := fs.Attributes[storageLabel]; ok {
  1083  			params.storageLabels = append([]string{fmt.Sprintf("%v", storageLabel)}, params.storageLabels...)
  1084  		}
  1085  		params.storageConfig, err = newStorageConfig(fs.Attributes, defaultStorageClass)
  1086  		if err != nil {
  1087  			return errors.Annotatef(err, "invalid storage configuration for %v", fs.StorageName)
  1088  		}
  1089  
  1090  		pvcSpec, err := k.maybeGetVolumeClaimSpec(params)
  1091  		if err != nil {
  1092  			return errors.Annotatef(err, "finding volume for %s", fs.StorageName)
  1093  		}
  1094  		tags := make(map[string]string)
  1095  		for k, v := range fs.ResourceTags {
  1096  			tags[k] = v
  1097  		}
  1098  		tags[labelStorage] = fs.StorageName
  1099  		tags[labelApplication] = appName
  1100  		pvc := core.PersistentVolumeClaim{
  1101  			ObjectMeta: v1.ObjectMeta{
  1102  				Name:   params.pvcName,
  1103  				Labels: tags},
  1104  			Spec: *pvcSpec,
  1105  		}
  1106  		logger.Debugf("using persistent volume claim for %s filesystem %s: %+v", appName, fs.StorageName, pvc)
  1107  		statefulSet.VolumeClaimTemplates = append(statefulSet.VolumeClaimTemplates, pvc)
  1108  		podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, core.VolumeMount{
  1109  			Name:      pvc.Name,
  1110  			MountPath: mountPath,
  1111  		})
  1112  	}
  1113  	return nil
  1114  }
  1115  
  1116  func (k *kubernetesClient) configureDevices(unitSpec *unitSpec, devices []devices.KubernetesDeviceParams) error {
  1117  	for i := range unitSpec.Pod.Containers {
  1118  		resources := unitSpec.Pod.Containers[i].Resources
  1119  		for _, dev := range devices {
  1120  			err := mergeDeviceConstraints(dev, &resources)
  1121  			if err != nil {
  1122  				return errors.Annotatef(err, "merging device constraint %+v to %#v", dev, resources)
  1123  			}
  1124  		}
  1125  		unitSpec.Pod.Containers[i].Resources = resources
  1126  	}
  1127  	nodeLabel, err := getNodeSelectorFromDeviceConstraints(devices)
  1128  	if err != nil {
  1129  		return err
  1130  	}
  1131  	if nodeLabel != "" {
  1132  		unitSpec.Pod.NodeSelector = buildNodeSelector(nodeLabel)
  1133  	}
  1134  	return nil
  1135  }
  1136  
  1137  func (k *kubernetesClient) configureConstraint(unitSpec *unitSpec, constraint, value string) error {
  1138  	for i := range unitSpec.Pod.Containers {
  1139  		resources := unitSpec.Pod.Containers[i].Resources
  1140  		err := mergeConstraint(constraint, value, &resources)
  1141  		if err != nil {
  1142  			return errors.Annotatef(err, "merging constraint %q to %#v", constraint, resources)
  1143  		}
  1144  		unitSpec.Pod.Containers[i].Resources = resources
  1145  	}
  1146  	return nil
  1147  }
  1148  
  1149  type configMapNameFunc func(fileSetName string) string
  1150  
  1151  func (k *kubernetesClient) configurePodFiles(podSpec *core.PodSpec, containers []caas.ContainerSpec, cfgMapName configMapNameFunc) error {
  1152  	for i, container := range containers {
  1153  		for _, fileSet := range container.Files {
  1154  			cfgName := cfgMapName(fileSet.Name)
  1155  			vol := core.Volume{Name: cfgName}
  1156  			if err := k.ensureConfigMap(filesetConfigMap(cfgName, &fileSet)); err != nil {
  1157  				return errors.Annotatef(err, "creating or updating ConfigMap for file set %v", cfgName)
  1158  			}
  1159  			vol.ConfigMap = &core.ConfigMapVolumeSource{
  1160  				LocalObjectReference: core.LocalObjectReference{
  1161  					Name: cfgName,
  1162  				},
  1163  			}
  1164  			podSpec.Volumes = append(podSpec.Volumes, vol)
  1165  			podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, core.VolumeMount{
  1166  				Name:      cfgName,
  1167  				MountPath: fileSet.MountPath,
  1168  			})
  1169  		}
  1170  	}
  1171  	return nil
  1172  }
  1173  
  1174  func (k *kubernetesClient) configureDeployment(
  1175  	appName, deploymentName string, labels map[string]string, unitSpec *unitSpec, containers []caas.ContainerSpec, replicas *int32,
  1176  ) error {
  1177  	logger.Debugf("creating/updating deployment for %s", appName)
  1178  
  1179  	// Add the specified file to the pod spec.
  1180  	cfgName := func(fileSetName string) string {
  1181  		return applicationConfigMapName(deploymentName, fileSetName)
  1182  	}
  1183  	podSpec := unitSpec.Pod
  1184  	if err := k.configurePodFiles(&podSpec, containers, cfgName); err != nil {
  1185  		return errors.Trace(err)
  1186  	}
  1187  
  1188  	deployment := &apps.Deployment{
  1189  		ObjectMeta: v1.ObjectMeta{
  1190  			Name:   deploymentName,
  1191  			Labels: labels},
  1192  		Spec: apps.DeploymentSpec{
  1193  			Replicas: replicas,
  1194  			Selector: &v1.LabelSelector{
  1195  				MatchLabels: map[string]string{labelApplication: appName},
  1196  			},
  1197  			Template: core.PodTemplateSpec{
  1198  				ObjectMeta: v1.ObjectMeta{
  1199  					GenerateName: deploymentName + "-",
  1200  					Labels:       labels,
  1201  				},
  1202  				Spec: podSpec,
  1203  			},
  1204  		},
  1205  	}
  1206  	return k.ensureDeployment(deployment)
  1207  }
  1208  
  1209  func (k *kubernetesClient) ensureDeployment(spec *apps.Deployment) error {
  1210  	deployments := k.AppsV1().Deployments(k.namespace)
  1211  	_, err := deployments.Update(spec)
  1212  	if k8serrors.IsNotFound(err) {
  1213  		_, err = deployments.Create(spec)
  1214  	}
  1215  	return errors.Trace(err)
  1216  }
  1217  
  1218  func (k *kubernetesClient) deleteDeployment(name string) error {
  1219  	deployments := k.AppsV1().Deployments(k.namespace)
  1220  	err := deployments.Delete(name, &v1.DeleteOptions{
  1221  		PropagationPolicy: &defaultPropagationPolicy,
  1222  	})
  1223  	if k8serrors.IsNotFound(err) {
  1224  		return nil
  1225  	}
  1226  	return errors.Trace(err)
  1227  }
  1228  
  1229  func (k *kubernetesClient) configureStatefulSet(
  1230  	appName, deploymentName string, labels map[string]string, unitSpec *unitSpec,
  1231  	containers []caas.ContainerSpec, replicas *int32, filesystems []storage.KubernetesFilesystemParams,
  1232  ) error {
  1233  	logger.Debugf("creating/updating stateful set for %s", appName)
  1234  
  1235  	// Add the specified file to the pod spec.
  1236  	cfgName := func(fileSetName string) string {
  1237  		return applicationConfigMapName(deploymentName, fileSetName)
  1238  	}
  1239  	statefulset := &apps.StatefulSet{
  1240  		ObjectMeta: v1.ObjectMeta{
  1241  			Name:   deploymentName,
  1242  			Labels: labels},
  1243  		Spec: apps.StatefulSetSpec{
  1244  			Replicas: replicas,
  1245  			Selector: &v1.LabelSelector{
  1246  				MatchLabels: map[string]string{labelApplication: appName},
  1247  			},
  1248  			Template: core.PodTemplateSpec{
  1249  				ObjectMeta: v1.ObjectMeta{
  1250  					Labels: labels,
  1251  				},
  1252  			},
  1253  			PodManagementPolicy: apps.ParallelPodManagement,
  1254  		},
  1255  	}
  1256  	podSpec := unitSpec.Pod
  1257  	if err := k.configurePodFiles(&podSpec, containers, cfgName); err != nil {
  1258  		return errors.Trace(err)
  1259  	}
  1260  	existingPodSpec := podSpec
  1261  
  1262  	// Create a new stateful set with the necessary storage config.
  1263  	legacy := isLegacyName(deploymentName)
  1264  	if err := k.configureStorage(&podSpec, &statefulset.Spec, appName, legacy, filesystems); err != nil {
  1265  		return errors.Annotatef(err, "configuring storage for %s", appName)
  1266  	}
  1267  	statefulset.Spec.Template.Spec = podSpec
  1268  	return k.ensureStatefulSet(statefulset, existingPodSpec)
  1269  }
  1270  
  1271  func (k *kubernetesClient) ensureStatefulSet(spec *apps.StatefulSet, existingPodSpec core.PodSpec) error {
  1272  	statefulsets := k.AppsV1().StatefulSets(k.namespace)
  1273  	_, err := statefulsets.Update(spec)
  1274  	if k8serrors.IsNotFound(err) {
  1275  		_, err = statefulsets.Create(spec)
  1276  	}
  1277  	if !k8serrors.IsInvalid(err) {
  1278  		return errors.Trace(err)
  1279  	}
  1280  
  1281  	// The statefulset already exists so all we are allowed to update is replicas,
  1282  	// template, update strategy. Juju may hand out info with a slightly different
  1283  	// requested volume size due to trying to adapt the unit model to the k8s world.
  1284  	existing, err := statefulsets.Get(spec.Name, v1.GetOptions{IncludeUninitialized: true})
  1285  	if err != nil {
  1286  		return errors.Trace(err)
  1287  	}
  1288  	// TODO(caas) - allow extra storage to be added
  1289  	existing.Spec.Replicas = spec.Spec.Replicas
  1290  	existing.Spec.Template.Spec.Containers = existingPodSpec.Containers
  1291  	_, err = statefulsets.Update(existing)
  1292  	return errors.Trace(err)
  1293  }
  1294  
  1295  func (k *kubernetesClient) deleteStatefulSet(name string) error {
  1296  	deployments := k.AppsV1().StatefulSets(k.namespace)
  1297  	err := deployments.Delete(name, &v1.DeleteOptions{
  1298  		PropagationPolicy: &defaultPropagationPolicy,
  1299  	})
  1300  	if k8serrors.IsNotFound(err) {
  1301  		return nil
  1302  	}
  1303  	return errors.Trace(err)
  1304  }
  1305  
  1306  func (k *kubernetesClient) deleteVolumeClaims(appName string, p *core.Pod) ([]string, error) {
  1307  	volumesByName := make(map[string]core.Volume)
  1308  	for _, pv := range p.Spec.Volumes {
  1309  		volumesByName[pv.Name] = pv
  1310  	}
  1311  
  1312  	var deletedClaimVolumes []string
  1313  	for _, volMount := range p.Spec.Containers[0].VolumeMounts {
  1314  		vol, ok := volumesByName[volMount.Name]
  1315  		if !ok {
  1316  			logger.Warningf("volume for volume mount %q not found", volMount.Name)
  1317  			continue
  1318  		}
  1319  		if vol.PersistentVolumeClaim == nil {
  1320  			// Ignore volumes which are not Juju managed filesystems.
  1321  			continue
  1322  		}
  1323  		pvClaims := k.CoreV1().PersistentVolumeClaims(k.namespace)
  1324  		err := pvClaims.Delete(vol.PersistentVolumeClaim.ClaimName, &v1.DeleteOptions{
  1325  			PropagationPolicy: &defaultPropagationPolicy,
  1326  		})
  1327  		if err != nil && !k8serrors.IsNotFound(err) {
  1328  			return nil, errors.Annotatef(err, "deleting persistent volume claim %v for %v",
  1329  				vol.PersistentVolumeClaim.ClaimName, p.Name)
  1330  		}
  1331  		deletedClaimVolumes = append(deletedClaimVolumes, vol.Name)
  1332  	}
  1333  	return deletedClaimVolumes, nil
  1334  }
  1335  
  1336  func (k *kubernetesClient) configureService(
  1337  	appName, deploymentName string, containerPorts []core.ContainerPort,
  1338  	tags map[string]string, config application.ConfigAttributes,
  1339  ) error {
  1340  	logger.Debugf("creating/updating service for %s", appName)
  1341  
  1342  	var ports []core.ServicePort
  1343  	for i, cp := range containerPorts {
  1344  		// We normally expect a single container port for most use cases.
  1345  		// We allow the user to specify what first service port should be,
  1346  		// otherwise it just defaults to the container port.
  1347  		// TODO(caas) - consider allowing all service ports to be specified
  1348  		var targetPort intstr.IntOrString
  1349  		if i == 0 {
  1350  			targetPort = intstr.FromInt(config.GetInt(serviceTargetPortConfigKey, int(cp.ContainerPort)))
  1351  		}
  1352  		ports = append(ports, core.ServicePort{
  1353  			Name:       cp.Name,
  1354  			Protocol:   cp.Protocol,
  1355  			Port:       cp.ContainerPort,
  1356  			TargetPort: targetPort,
  1357  		})
  1358  	}
  1359  
  1360  	serviceType := core.ServiceType(config.GetString(serviceTypeConfigKey, defaultServiceType))
  1361  	annotations, err := config.GetStringMap(serviceAnnotationsKey, nil)
  1362  	if err != nil {
  1363  		return errors.Annotatef(err, "unexpected annotations: %#v", config.Get(serviceAnnotationsKey, nil))
  1364  	}
  1365  	service := &core.Service{
  1366  		ObjectMeta: v1.ObjectMeta{
  1367  			Name:        deploymentName,
  1368  			Labels:      tags,
  1369  			Annotations: annotations,
  1370  		},
  1371  		Spec: core.ServiceSpec{
  1372  			Selector:                 map[string]string{labelApplication: appName},
  1373  			Type:                     serviceType,
  1374  			Ports:                    ports,
  1375  			ExternalIPs:              config.Get(serviceExternalIPsConfigKey, []string(nil)).([]string),
  1376  			LoadBalancerIP:           config.GetString(serviceLoadBalancerIPKey, ""),
  1377  			LoadBalancerSourceRanges: config.Get(serviceLoadBalancerSourceRangesKey, []string(nil)).([]string),
  1378  			ExternalName:             config.GetString(serviceExternalNameKey, ""),
  1379  		},
  1380  	}
  1381  	return k.ensureService(service)
  1382  }
  1383  
  1384  func (k *kubernetesClient) ensureService(spec *core.Service) error {
  1385  	services := k.CoreV1().Services(k.namespace)
  1386  	// Set any immutable fields if the service already exists.
  1387  	existing, err := services.Get(spec.Name, v1.GetOptions{IncludeUninitialized: true})
  1388  	if err == nil {
  1389  		spec.Spec.ClusterIP = existing.Spec.ClusterIP
  1390  		spec.ObjectMeta.ResourceVersion = existing.ObjectMeta.ResourceVersion
  1391  	}
  1392  	_, err = services.Update(spec)
  1393  	if k8serrors.IsNotFound(err) {
  1394  		_, err = services.Create(spec)
  1395  	}
  1396  	return errors.Trace(err)
  1397  }
  1398  
  1399  func (k *kubernetesClient) deleteService(deploymentName string) error {
  1400  	services := k.CoreV1().Services(k.namespace)
  1401  	err := services.Delete(deploymentName, &v1.DeleteOptions{
  1402  		PropagationPolicy: &defaultPropagationPolicy,
  1403  	})
  1404  	if k8serrors.IsNotFound(err) {
  1405  		return nil
  1406  	}
  1407  	return errors.Trace(err)
  1408  }
  1409  
  1410  // ExposeService sets up external access to the specified application.
  1411  func (k *kubernetesClient) ExposeService(appName string, resourceTags map[string]string, config application.ConfigAttributes) error {
  1412  	logger.Debugf("creating/updating ingress resource for %s", appName)
  1413  
  1414  	host := config.GetString(caas.JujuExternalHostNameKey, "")
  1415  	if host == "" {
  1416  		return errors.Errorf("external hostname required")
  1417  	}
  1418  	ingressClass := config.GetString(ingressClassKey, defaultIngressClass)
  1419  	ingressSSLRedirect := config.GetBool(ingressSSLRedirectKey, defaultIngressSSLRedirect)
  1420  	ingressSSLPassthrough := config.GetBool(ingressSSLPassthroughKey, defaultIngressSSLPassthrough)
  1421  	ingressAllowHTTP := config.GetBool(ingressAllowHTTPKey, defaultIngressAllowHTTPKey)
  1422  	httpPath := config.GetString(caas.JujuApplicationPath, caas.JujuDefaultApplicationPath)
  1423  	if httpPath == "$appname" {
  1424  		httpPath = appName
  1425  	}
  1426  	if !strings.HasPrefix(httpPath, "/") {
  1427  		httpPath = "/" + httpPath
  1428  	}
  1429  
  1430  	deploymentName := k.deploymentName(appName)
  1431  	svc, err := k.CoreV1().Services(k.namespace).Get(deploymentName, v1.GetOptions{})
  1432  	if err != nil {
  1433  		return errors.Trace(err)
  1434  	}
  1435  	if len(svc.Spec.Ports) == 0 {
  1436  		return errors.Errorf("cannot create ingress rule for service %q without a port", svc.Name)
  1437  	}
  1438  	spec := &v1beta1.Ingress{
  1439  		ObjectMeta: v1.ObjectMeta{
  1440  			Name:   deploymentName,
  1441  			Labels: resourceTags,
  1442  			Annotations: map[string]string{
  1443  				"ingress.kubernetes.io/rewrite-target":  "",
  1444  				"ingress.kubernetes.io/ssl-redirect":    strconv.FormatBool(ingressSSLRedirect),
  1445  				"kubernetes.io/ingress.class":           ingressClass,
  1446  				"kubernetes.io/ingress.allow-http":      strconv.FormatBool(ingressAllowHTTP),
  1447  				"ingress.kubernetes.io/ssl-passthrough": strconv.FormatBool(ingressSSLPassthrough),
  1448  			},
  1449  		},
  1450  		Spec: v1beta1.IngressSpec{
  1451  			Rules: []v1beta1.IngressRule{{
  1452  				Host: host,
  1453  				IngressRuleValue: v1beta1.IngressRuleValue{
  1454  					HTTP: &v1beta1.HTTPIngressRuleValue{
  1455  						Paths: []v1beta1.HTTPIngressPath{{
  1456  							Path: httpPath,
  1457  							Backend: v1beta1.IngressBackend{
  1458  								ServiceName: svc.Name, ServicePort: svc.Spec.Ports[0].TargetPort},
  1459  						}}},
  1460  				}}},
  1461  		},
  1462  	}
  1463  	return k.ensureIngress(spec)
  1464  }
  1465  
  1466  // UnexposeService removes external access to the specified service.
  1467  func (k *kubernetesClient) UnexposeService(appName string) error {
  1468  	logger.Debugf("deleting ingress resource for %s", appName)
  1469  	return k.deleteIngress(appName)
  1470  }
  1471  
  1472  func (k *kubernetesClient) ensureIngress(spec *v1beta1.Ingress) error {
  1473  	ingress := k.ExtensionsV1beta1().Ingresses(k.namespace)
  1474  	_, err := ingress.Update(spec)
  1475  	if k8serrors.IsNotFound(err) {
  1476  		_, err = ingress.Create(spec)
  1477  	}
  1478  	return errors.Trace(err)
  1479  }
  1480  
  1481  func (k *kubernetesClient) deleteIngress(appName string) error {
  1482  	deploymentName := k.deploymentName(appName)
  1483  	ingress := k.ExtensionsV1beta1().Ingresses(k.namespace)
  1484  	err := ingress.Delete(deploymentName, &v1.DeleteOptions{
  1485  		PropagationPolicy: &defaultPropagationPolicy,
  1486  	})
  1487  	if k8serrors.IsNotFound(err) {
  1488  		return nil
  1489  	}
  1490  	return errors.Trace(err)
  1491  }
  1492  
  1493  func operatorSelector(appName string) string {
  1494  	return fmt.Sprintf("%v==%v", labelOperator, appName)
  1495  }
  1496  
  1497  func applicationSelector(appName string) string {
  1498  	return fmt.Sprintf("%v==%v", labelApplication, appName)
  1499  }
  1500  
  1501  // WatchUnits returns a watcher which notifies when there
  1502  // are changes to units of the specified application.
  1503  func (k *kubernetesClient) WatchUnits(appName string) (watcher.NotifyWatcher, error) {
  1504  	pods := k.CoreV1().Pods(k.namespace)
  1505  	w, err := pods.Watch(v1.ListOptions{
  1506  		LabelSelector: applicationSelector(appName),
  1507  		Watch:         true,
  1508  	})
  1509  	if err != nil {
  1510  		return nil, errors.Trace(err)
  1511  	}
  1512  	return k.newWatcher(w, appName, k.clock)
  1513  }
  1514  
  1515  // WatchOperator returns a watcher which notifies when there
  1516  // are changes to the operator of the specified application.
  1517  func (k *kubernetesClient) WatchOperator(appName string) (watcher.NotifyWatcher, error) {
  1518  	pods := k.CoreV1().Pods(k.namespace)
  1519  	w, err := pods.Watch(v1.ListOptions{
  1520  		LabelSelector: operatorSelector(appName),
  1521  		Watch:         true,
  1522  	})
  1523  	if err != nil {
  1524  		return nil, errors.Trace(err)
  1525  	}
  1526  	return k.newWatcher(w, appName, k.clock)
  1527  }
  1528  
  1529  // legacyJujuPVNameRegexp matches how Juju labels persistent volumes.
  1530  // The pattern is: juju-<storagename>-<digit>
  1531  var legacyJujuPVNameRegexp = regexp.MustCompile(`^juju-(?P<storageName>\D+)-\d+$`)
  1532  
  1533  // jujuPVNameRegexp matches how Juju labels persistent volumes.
  1534  // The pattern is: <storagename>-<digit>
  1535  var jujuPVNameRegexp = regexp.MustCompile(`^(?P<storageName>\D+)-\d+$`)
  1536  
  1537  // Units returns all units and any associated filesystems of the specified application.
  1538  // Filesystems are mounted via volumes bound to the unit.
  1539  func (k *kubernetesClient) Units(appName string) ([]caas.Unit, error) {
  1540  	pods := k.CoreV1().Pods(k.namespace)
  1541  	podsList, err := pods.List(v1.ListOptions{
  1542  		LabelSelector: applicationSelector(appName),
  1543  	})
  1544  	if err != nil {
  1545  		return nil, errors.Trace(err)
  1546  	}
  1547  
  1548  	var units []caas.Unit
  1549  	now := time.Now()
  1550  	for _, p := range podsList.Items {
  1551  		var ports []string
  1552  		for _, c := range p.Spec.Containers {
  1553  			for _, p := range c.Ports {
  1554  				ports = append(ports, fmt.Sprintf("%v/%v", p.ContainerPort, p.Protocol))
  1555  			}
  1556  		}
  1557  		terminated := p.DeletionTimestamp != nil
  1558  		statusMessage, unitStatus, since, err := k.getPODStatus(p, now)
  1559  		if err != nil {
  1560  			return nil, errors.Trace(err)
  1561  		}
  1562  		unitInfo := caas.Unit{
  1563  			Id:      string(p.UID),
  1564  			Address: p.Status.PodIP,
  1565  			Ports:   ports,
  1566  			Dying:   terminated,
  1567  			Status: status.StatusInfo{
  1568  				Status:  unitStatus,
  1569  				Message: statusMessage,
  1570  				Since:   &since,
  1571  			},
  1572  		}
  1573  
  1574  		volumesByName := make(map[string]core.Volume)
  1575  		for _, pv := range p.Spec.Volumes {
  1576  			volumesByName[pv.Name] = pv
  1577  		}
  1578  		pVolumes := k.CoreV1().PersistentVolumes()
  1579  
  1580  		// Gather info about how filesystems are attached/mounted to the pod.
  1581  		// The mount name represents the filesystem tag name used by Juju.
  1582  		for _, volMount := range p.Spec.Containers[0].VolumeMounts {
  1583  			vol, ok := volumesByName[volMount.Name]
  1584  			if !ok {
  1585  				logger.Warningf("volume for volume mount %q not found", volMount.Name)
  1586  				continue
  1587  			}
  1588  			if vol.PersistentVolumeClaim == nil || vol.PersistentVolumeClaim.ClaimName == "" {
  1589  				// Ignore volumes which are not Juju managed filesystems.
  1590  				logger.Debugf("Ignoring blank PersistentVolumeClaim or ClaimName")
  1591  				continue
  1592  			}
  1593  			pvClaims := k.CoreV1().PersistentVolumeClaims(k.namespace)
  1594  			pvc, err := pvClaims.Get(vol.PersistentVolumeClaim.ClaimName, v1.GetOptions{})
  1595  			if k8serrors.IsNotFound(err) {
  1596  				// Ignore claims which don't exist (yet).
  1597  				continue
  1598  			}
  1599  			if err != nil {
  1600  				return nil, errors.Annotate(err, "unable to get persistent volume claim")
  1601  			}
  1602  
  1603  			if pvc.Status.Phase == core.ClaimPending {
  1604  				logger.Debugf(fmt.Sprintf("PersistentVolumeClaim for %v is pending", vol.PersistentVolumeClaim.ClaimName))
  1605  				continue
  1606  			}
  1607  			pv, err := pVolumes.Get(pvc.Spec.VolumeName, v1.GetOptions{})
  1608  			if k8serrors.IsNotFound(err) {
  1609  				// Ignore volumes which don't exist (yet).
  1610  				continue
  1611  			}
  1612  			if err != nil {
  1613  				return nil, errors.Annotate(err, "unable to get persistent volume")
  1614  			}
  1615  
  1616  			storageName := pvc.Labels[labelStorage]
  1617  			if storageName == "" {
  1618  				if valid := legacyJujuPVNameRegexp.MatchString(volMount.Name); valid {
  1619  					storageName = legacyJujuPVNameRegexp.ReplaceAllString(volMount.Name, "$storageName")
  1620  				} else if valid := jujuPVNameRegexp.MatchString(volMount.Name); valid {
  1621  					storageName = jujuPVNameRegexp.ReplaceAllString(volMount.Name, "$storageName")
  1622  				}
  1623  			}
  1624  			statusMessage := ""
  1625  			since = now
  1626  			if len(pvc.Status.Conditions) > 0 {
  1627  				statusMessage = pvc.Status.Conditions[0].Message
  1628  				since = pvc.Status.Conditions[0].LastProbeTime.Time
  1629  			}
  1630  			if statusMessage == "" {
  1631  				// If there are any events for this pvc we can use the
  1632  				// most recent to set the status.
  1633  				events := k.CoreV1().Events(k.namespace)
  1634  				eventList, err := events.List(v1.ListOptions{
  1635  					IncludeUninitialized: true,
  1636  					FieldSelector:        fields.OneTermEqualSelector("involvedObject.name", pvc.Name).String(),
  1637  				})
  1638  				if err != nil {
  1639  					return nil, errors.Annotate(err, "unable to get events for PVC")
  1640  				}
  1641  				// Take the most recent event.
  1642  				if count := len(eventList.Items); count > 0 {
  1643  					statusMessage = eventList.Items[count-1].Message
  1644  				}
  1645  			}
  1646  
  1647  			unitInfo.FilesystemInfo = append(unitInfo.FilesystemInfo, caas.FilesystemInfo{
  1648  				StorageName:  storageName,
  1649  				Size:         uint64(vol.PersistentVolumeClaim.Size()),
  1650  				FilesystemId: pvc.Name,
  1651  				MountPoint:   volMount.MountPath,
  1652  				ReadOnly:     volMount.ReadOnly,
  1653  				Status: status.StatusInfo{
  1654  					Status:  k.jujuFilesystemStatus(pvc.Status.Phase),
  1655  					Message: statusMessage,
  1656  					Since:   &since,
  1657  				},
  1658  				Volume: caas.VolumeInfo{
  1659  					VolumeId:   pv.Name,
  1660  					Size:       uint64(pv.Size()),
  1661  					Persistent: pv.Spec.PersistentVolumeReclaimPolicy == core.PersistentVolumeReclaimRetain,
  1662  					Status: status.StatusInfo{
  1663  						Status:  k.jujuVolumeStatus(pv.Status.Phase),
  1664  						Message: pv.Status.Message,
  1665  						Since:   &since,
  1666  					},
  1667  				},
  1668  			})
  1669  		}
  1670  		units = append(units, unitInfo)
  1671  	}
  1672  	return units, nil
  1673  }
  1674  
  1675  // Operator returns an Operator with current status and life details.
  1676  func (k *kubernetesClient) Operator(appName string) (*caas.Operator, error) {
  1677  	pods := k.CoreV1().Pods(k.namespace)
  1678  	podsList, err := pods.List(v1.ListOptions{
  1679  		LabelSelector: operatorSelector(appName),
  1680  	})
  1681  	if err != nil {
  1682  		return nil, errors.Trace(err)
  1683  	}
  1684  	if len(podsList.Items) == 0 {
  1685  		return nil, errors.NotFoundf("operator pod for application %q", appName)
  1686  	}
  1687  
  1688  	opPod := podsList.Items[0]
  1689  	terminated := opPod.DeletionTimestamp != nil
  1690  	now := time.Now()
  1691  	statusMessage, opStatus, since, err := k.getPODStatus(opPod, now)
  1692  	return &caas.Operator{
  1693  		Id:    string(opPod.UID),
  1694  		Dying: terminated,
  1695  		Status: status.StatusInfo{
  1696  			Status:  opStatus,
  1697  			Message: statusMessage,
  1698  			Since:   &since,
  1699  		},
  1700  	}, nil
  1701  }
  1702  
  1703  func (k *kubernetesClient) getPODStatus(pod core.Pod, now time.Time) (string, status.Status, time.Time, error) {
  1704  	terminated := pod.DeletionTimestamp != nil
  1705  	jujuStatus := k.jujuStatus(pod.Status.Phase, terminated)
  1706  	statusMessage := pod.Status.Message
  1707  	since := now
  1708  	if statusMessage == "" {
  1709  		for _, cond := range pod.Status.Conditions {
  1710  			statusMessage = cond.Message
  1711  			since = cond.LastProbeTime.Time
  1712  			if cond.Type == core.PodScheduled && cond.Reason == core.PodReasonUnschedulable {
  1713  				jujuStatus = status.Blocked
  1714  				break
  1715  			}
  1716  		}
  1717  	}
  1718  
  1719  	if statusMessage == "" {
  1720  		// If there are any events for this pod we can use the
  1721  		// most recent to set the status.
  1722  		events := k.CoreV1().Events(k.namespace)
  1723  		eventList, err := events.List(v1.ListOptions{
  1724  			IncludeUninitialized: true,
  1725  			FieldSelector:        fields.OneTermEqualSelector("involvedObject.name", pod.Name).String(),
  1726  		})
  1727  		if err != nil {
  1728  			return "", "", time.Time{}, errors.Trace(err)
  1729  		}
  1730  		// Take the most recent event.
  1731  		if count := len(eventList.Items); count > 0 {
  1732  			statusMessage = eventList.Items[count-1].Message
  1733  		}
  1734  	}
  1735  
  1736  	return statusMessage, jujuStatus, since, nil
  1737  }
  1738  
  1739  func (k *kubernetesClient) jujuStatus(podPhase core.PodPhase, terminated bool) status.Status {
  1740  	if terminated {
  1741  		return status.Terminated
  1742  	}
  1743  	switch podPhase {
  1744  	case core.PodRunning:
  1745  		return status.Running
  1746  	case core.PodFailed:
  1747  		return status.Error
  1748  	case core.PodPending:
  1749  		return status.Allocating
  1750  	default:
  1751  		return status.Unknown
  1752  	}
  1753  }
  1754  
  1755  func (k *kubernetesClient) jujuFilesystemStatus(pvcPhase core.PersistentVolumeClaimPhase) status.Status {
  1756  	switch pvcPhase {
  1757  	case core.ClaimPending:
  1758  		return status.Pending
  1759  	case core.ClaimBound:
  1760  		return status.Attached
  1761  	case core.ClaimLost:
  1762  		return status.Detached
  1763  	default:
  1764  		return status.Unknown
  1765  	}
  1766  }
  1767  
  1768  func (k *kubernetesClient) jujuVolumeStatus(pvPhase core.PersistentVolumePhase) status.Status {
  1769  	switch pvPhase {
  1770  	case core.VolumePending:
  1771  		return status.Pending
  1772  	case core.VolumeBound:
  1773  		return status.Attached
  1774  	case core.VolumeAvailable, core.VolumeReleased:
  1775  		return status.Detached
  1776  	case core.VolumeFailed:
  1777  		return status.Error
  1778  	default:
  1779  		return status.Unknown
  1780  	}
  1781  }
  1782  
  1783  // filesetConfigMap returns a *core.ConfigMap for a pod
  1784  // of the specified unit, with the specified files.
  1785  func filesetConfigMap(configMapName string, files *caas.FileSet) *core.ConfigMap {
  1786  	result := &core.ConfigMap{
  1787  		ObjectMeta: v1.ObjectMeta{
  1788  			Name: configMapName,
  1789  		},
  1790  		Data: map[string]string{},
  1791  	}
  1792  	for name, data := range files.Files {
  1793  		result.Data[name] = data
  1794  	}
  1795  	return result
  1796  }
  1797  
  1798  func (k *kubernetesClient) ensureConfigMap(configMap *core.ConfigMap) error {
  1799  	configMaps := k.CoreV1().ConfigMaps(k.namespace)
  1800  	_, err := configMaps.Update(configMap)
  1801  	if k8serrors.IsNotFound(err) {
  1802  		_, err = configMaps.Create(configMap)
  1803  	}
  1804  	return errors.Trace(err)
  1805  }
  1806  
  1807  // operatorPod returns a *core.Pod for the operator pod
  1808  // of the specified application.
  1809  func operatorPod(podName, appName, agentPath, operatorImagePath, version string, tags map[string]string) *core.Pod {
  1810  	configMapName := operatorConfigMapName(podName)
  1811  	configVolName := configMapName
  1812  
  1813  	if isLegacyName(podName) {
  1814  		configVolName += "-volume"
  1815  	}
  1816  
  1817  	appTag := names.NewApplicationTag(appName)
  1818  	podLabels := make(map[string]string)
  1819  	for k, v := range tags {
  1820  		podLabels[k] = v
  1821  	}
  1822  	podLabels[labelVersion] = version
  1823  	return &core.Pod{
  1824  		ObjectMeta: v1.ObjectMeta{
  1825  			Name:   podName,
  1826  			Labels: podLabels,
  1827  		},
  1828  		Spec: core.PodSpec{
  1829  			Containers: []core.Container{{
  1830  				Name:            "juju-operator",
  1831  				ImagePullPolicy: core.PullIfNotPresent,
  1832  				Image:           operatorImagePath,
  1833  				Env: []core.EnvVar{
  1834  					{Name: "JUJU_APPLICATION", Value: appName},
  1835  				},
  1836  				VolumeMounts: []core.VolumeMount{{
  1837  					Name:      configVolName,
  1838  					MountPath: filepath.Join(agent.Dir(agentPath, appTag), "template-agent.conf"),
  1839  					SubPath:   "template-agent.conf",
  1840  				}},
  1841  			}},
  1842  			Volumes: []core.Volume{{
  1843  				Name: configVolName,
  1844  				VolumeSource: core.VolumeSource{
  1845  					ConfigMap: &core.ConfigMapVolumeSource{
  1846  						LocalObjectReference: core.LocalObjectReference{
  1847  							Name: configMapName,
  1848  						},
  1849  						Items: []core.KeyToPath{{
  1850  							Key:  appName + "-agent.conf",
  1851  							Path: "template-agent.conf",
  1852  						}},
  1853  					},
  1854  				},
  1855  			}},
  1856  		},
  1857  	}
  1858  }
  1859  
  1860  // operatorConfigMap returns a *core.ConfigMap for the operator pod
  1861  // of the specified application, with the specified configuration.
  1862  func operatorConfigMap(appName, operatorName string, config *caas.OperatorConfig) *core.ConfigMap {
  1863  	configMapName := operatorConfigMapName(operatorName)
  1864  	return &core.ConfigMap{
  1865  		ObjectMeta: v1.ObjectMeta{
  1866  			Name: configMapName,
  1867  		},
  1868  		Data: map[string]string{
  1869  			appName + "-agent.conf": string(config.AgentConf),
  1870  		},
  1871  	}
  1872  }
  1873  
  1874  type unitSpec struct {
  1875  	Pod core.PodSpec `json:"pod"`
  1876  }
  1877  
  1878  var defaultPodTemplate = `
  1879  pod:
  1880    containers:
  1881    {{- range .Containers }}
  1882    - name: {{.Name}}
  1883      {{if .Ports}}
  1884      ports:
  1885      {{- range .Ports }}
  1886          - containerPort: {{.ContainerPort}}
  1887            {{if .Name}}name: {{.Name}}{{end}}
  1888            {{if .Protocol}}protocol: {{.Protocol}}{{end}}
  1889      {{- end}}
  1890      {{end}}
  1891      {{if .Command}}
  1892      command: [{{- range $idx, $c := .Command -}}{{if ne $idx 0}},{{end}}"{{$c}}"{{- end -}}]
  1893      {{end}}
  1894      {{if .Args}}
  1895      args: [{{- range $idx, $a := .Args -}}{{if ne $idx 0}},{{end}}"{{$a}}"{{- end -}}]
  1896      {{end}}
  1897      {{if .WorkingDir}}
  1898      workingDir: {{.WorkingDir}}
  1899      {{end}}
  1900      {{if .Config}}
  1901      env:
  1902      {{- range $k, $v := .Config }}
  1903          - name: {{$k}}
  1904            value: {{$v}}
  1905      {{- end}}
  1906      {{end}}
  1907    {{- end}}
  1908  `[1:]
  1909  
  1910  func makeUnitSpec(appName, deploymentName string, podSpec *caas.PodSpec) (*unitSpec, error) {
  1911  	// Fill out the easy bits using a template.
  1912  	tmpl := template.Must(template.New("").Parse(defaultPodTemplate))
  1913  	var buf bytes.Buffer
  1914  	if err := tmpl.Execute(&buf, podSpec); err != nil {
  1915  		return nil, errors.Trace(err)
  1916  	}
  1917  	unitSpecString := buf.String()
  1918  
  1919  	var unitSpec unitSpec
  1920  	decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(unitSpecString), len(unitSpecString))
  1921  	if err := decoder.Decode(&unitSpec); err != nil {
  1922  		logger.Errorf("unable to parse %q pod spec: %+v\n%v", appName, *podSpec, unitSpecString)
  1923  		return nil, errors.Trace(err)
  1924  	}
  1925  
  1926  	var imageSecretNames []core.LocalObjectReference
  1927  	// Now fill in the hard bits progamatically.
  1928  	for i, c := range podSpec.Containers {
  1929  		if c.Image != "" {
  1930  			logger.Warningf("Image parameter deprecated, use ImageDetails")
  1931  			unitSpec.Pod.Containers[i].Image = c.Image
  1932  		} else {
  1933  			unitSpec.Pod.Containers[i].Image = c.ImageDetails.ImagePath
  1934  		}
  1935  		if c.ImageDetails.Password != "" {
  1936  			imageSecretNames = append(imageSecretNames, core.LocalObjectReference{Name: appSecretName(deploymentName, c.Name)})
  1937  		}
  1938  
  1939  		if c.ProviderContainer == nil {
  1940  			continue
  1941  		}
  1942  		spec, ok := c.ProviderContainer.(*K8sContainerSpec)
  1943  		if !ok {
  1944  			return nil, errors.Errorf("unexpected kubernetes container spec type %T", c.ProviderContainer)
  1945  		}
  1946  		unitSpec.Pod.Containers[i].ImagePullPolicy = spec.ImagePullPolicy
  1947  		if spec.LivenessProbe != nil {
  1948  			unitSpec.Pod.Containers[i].LivenessProbe = spec.LivenessProbe
  1949  		}
  1950  		if spec.ReadinessProbe != nil {
  1951  			unitSpec.Pod.Containers[i].ReadinessProbe = spec.ReadinessProbe
  1952  		}
  1953  	}
  1954  	unitSpec.Pod.ImagePullSecrets = imageSecretNames
  1955  	if podSpec.ProviderPod != nil {
  1956  		spec, ok := podSpec.ProviderPod.(*K8sPodSpec)
  1957  		if !ok {
  1958  			return nil, errors.Errorf("unexpected kubernetes pod spec type %T", podSpec.ProviderPod)
  1959  		}
  1960  		unitSpec.Pod.ActiveDeadlineSeconds = spec.ActiveDeadlineSeconds
  1961  		unitSpec.Pod.ServiceAccountName = spec.ServiceAccountName
  1962  		unitSpec.Pod.TerminationGracePeriodSeconds = spec.TerminationGracePeriodSeconds
  1963  		unitSpec.Pod.Hostname = spec.Hostname
  1964  		unitSpec.Pod.Subdomain = spec.Subdomain
  1965  		unitSpec.Pod.DNSConfig = spec.DNSConfig
  1966  		unitSpec.Pod.DNSPolicy = spec.DNSPolicy
  1967  		unitSpec.Pod.Priority = spec.Priority
  1968  		unitSpec.Pod.PriorityClassName = spec.PriorityClassName
  1969  		unitSpec.Pod.SecurityContext = spec.SecurityContext
  1970  		unitSpec.Pod.RestartPolicy = spec.RestartPolicy
  1971  		unitSpec.Pod.AutomountServiceAccountToken = spec.AutomountServiceAccountToken
  1972  		unitSpec.Pod.ReadinessGates = spec.ReadinessGates
  1973  	}
  1974  	return &unitSpec, nil
  1975  }
  1976  
  1977  // legacyAppName returns true if there are any artifacts for
  1978  // appName which indicate that this deployment was for Juju 2.5.0.
  1979  func (k *kubernetesClient) legacyAppName(appName string) bool {
  1980  	statefulsets := k.AppsV1().StatefulSets(k.namespace)
  1981  	legacyName := "juju-operator-" + appName
  1982  	_, err := statefulsets.Get(legacyName, v1.GetOptions{IncludeUninitialized: true})
  1983  	return err == nil
  1984  }
  1985  
  1986  func (k *kubernetesClient) operatorName(appName string) string {
  1987  	if k.legacyAppName(appName) {
  1988  		return "juju-operator-" + appName
  1989  	}
  1990  	return appName + "-operator"
  1991  }
  1992  
  1993  func (k *kubernetesClient) deploymentName(appName string) string {
  1994  	if k.legacyAppName(appName) {
  1995  		return "juju-" + appName
  1996  	}
  1997  	return appName
  1998  }
  1999  
  2000  func isLegacyName(resourceName string) bool {
  2001  	return strings.HasPrefix(resourceName, "juju-")
  2002  }
  2003  
  2004  func operatorConfigMapName(operatorName string) string {
  2005  	return operatorName + "-config"
  2006  }
  2007  
  2008  func applicationConfigMapName(deploymentName, fileSetName string) string {
  2009  	return fmt.Sprintf("%v-%v-config", deploymentName, fileSetName)
  2010  }
  2011  
  2012  func appSecretName(deploymentName, containerName string) string {
  2013  	// A pod may have multiple containers with different images and thus different secrets
  2014  	return deploymentName + "-" + containerName + "-secret"
  2015  }
  2016  
  2017  func qualifiedStorageClassName(namespace, storageClass string) string {
  2018  	return namespace + "-" + storageClass
  2019  }
  2020  
  2021  func mergeDeviceConstraints(device devices.KubernetesDeviceParams, resources *core.ResourceRequirements) error {
  2022  	if resources.Limits == nil {
  2023  		resources.Limits = core.ResourceList{}
  2024  	}
  2025  	if resources.Requests == nil {
  2026  		resources.Requests = core.ResourceList{}
  2027  	}
  2028  
  2029  	resourceName := core.ResourceName(device.Type)
  2030  	if v, ok := resources.Limits[resourceName]; ok {
  2031  		return errors.NotValidf("resource limit for %q has already been set to %v! resource limit %q", resourceName, v, resourceName)
  2032  	}
  2033  	if v, ok := resources.Requests[resourceName]; ok {
  2034  		return errors.NotValidf("resource request for %q has already been set to %v! resource limit %q", resourceName, v, resourceName)
  2035  	}
  2036  	// GPU request/limit have to be set to same value equals to the Count.
  2037  	// - https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/#clusters-containing-different-types-of-nvidia-gpus
  2038  	resources.Limits[resourceName] = *resource.NewQuantity(device.Count, resource.DecimalSI)
  2039  	resources.Requests[resourceName] = *resource.NewQuantity(device.Count, resource.DecimalSI)
  2040  	return nil
  2041  }
  2042  
  2043  func mergeConstraint(constraint string, value string, resources *core.ResourceRequirements) error {
  2044  	if resources.Limits == nil {
  2045  		resources.Limits = core.ResourceList{}
  2046  	}
  2047  	resourceName := core.ResourceName(constraint)
  2048  	if v, ok := resources.Limits[resourceName]; ok {
  2049  		return errors.NotValidf("resource limit for %q has already been set to %v!", resourceName, v)
  2050  	}
  2051  	parsedValue, err := resource.ParseQuantity(value)
  2052  	if err != nil {
  2053  		return errors.Annotatef(err, "invalid constraint value %q for %v", value, constraint)
  2054  	}
  2055  	resources.Limits[resourceName] = parsedValue
  2056  	return nil
  2057  }
  2058  
  2059  func buildNodeSelector(nodeLabel string) map[string]string {
  2060  	// TODO(caas): to support GKE, set it to `cloud.google.com/gke-accelerator`,
  2061  	// current only set to generic `accelerator` because we do not have k8s provider concept yet.
  2062  	key := "accelerator"
  2063  	return map[string]string{key: nodeLabel}
  2064  }
  2065  
  2066  func getNodeSelectorFromDeviceConstraints(devices []devices.KubernetesDeviceParams) (string, error) {
  2067  	var nodeSelector string
  2068  	for _, device := range devices {
  2069  		if device.Attributes == nil {
  2070  			continue
  2071  		}
  2072  		if label, ok := device.Attributes[gpuAffinityNodeSelectorKey]; ok {
  2073  			if nodeSelector != "" && nodeSelector != label {
  2074  				return "", errors.NotValidf(
  2075  					"node affinity labels have to be same for all device constraints in same pod - containers in same pod are scheduled in same node.")
  2076  			}
  2077  			nodeSelector = label
  2078  		}
  2079  	}
  2080  	return nodeSelector, nil
  2081  }