Skip to content

Commit

Permalink
Add resource tags to resource provisioning info
Browse files Browse the repository at this point in the history
We now pass tags to set on resources (initially
instances, volumes and filesystems) through their
provisioning parameters, via the API.

We introduce a new environs/tags package that holds
the names we use for common tags, as well as helpers
for creating tag sets.
  • Loading branch information
axw committed Jun 3, 2015
1 parent f479fac commit 5cb1112
Show file tree
Hide file tree
Showing 41 changed files with 657 additions and 199 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ cmd/builddb/builddb
cmd/charmd/charmd
cmd/charmload/charmload
tags
!tags/
TAGS
!TAGS/
.emacs.desktop
.emacs.desktop.lock
*.test
Expand Down
2 changes: 1 addition & 1 deletion api/facadeversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var facadeVersions = map[string]int{
"Networker": 0,
"NotifyWatcher": 0,
"Pinger": 0,
"Provisioner": 0,
"Provisioner": 1,
"Reboot": 1,
"RelationUnitsWatcher": 0,
"Rsyslog": 0,
Expand Down
14 changes: 13 additions & 1 deletion apiserver/common/filesystems.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/juju/names"

"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/state"
"github.com/juju/juju/storage/poolmanager"
)
Expand All @@ -26,7 +27,12 @@ func IsFilesystemAlreadyProvisioned(err error) bool {
}

// FilesystemParams returns the parameters for creating the given filesystem.
func FilesystemParams(f state.Filesystem, poolManager poolmanager.PoolManager) (params.FilesystemParams, error) {
func FilesystemParams(
f state.Filesystem,
storageInstance state.StorageInstance,
environConfig *config.Config,
poolManager poolmanager.PoolManager,
) (params.FilesystemParams, error) {
stateFilesystemParams, ok := f.Params()
if !ok {
err := &filesystemAlreadyProvisionedError{fmt.Errorf(
Expand All @@ -35,6 +41,11 @@ func FilesystemParams(f state.Filesystem, poolManager poolmanager.PoolManager) (
return params.FilesystemParams{}, err
}

filesystemTags, err := storageTags(storageInstance, environConfig)
if err != nil {
return params.FilesystemParams{}, errors.Annotate(err, "computing storage tags")
}

providerType, cfg, err := StoragePoolConfig(stateFilesystemParams.Pool, poolManager)
if err != nil {
return params.FilesystemParams{}, errors.Trace(err)
Expand All @@ -45,6 +56,7 @@ func FilesystemParams(f state.Filesystem, poolManager poolmanager.PoolManager) (
stateFilesystemParams.Size,
string(providerType),
cfg.Attrs(),
filesystemTags,
nil, // attachment params set by the caller
}

Expand Down
33 changes: 33 additions & 0 deletions apiserver/common/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/juju/errors"
"github.com/juju/names"

"github.com/juju/juju/environs/config"
"github.com/juju/juju/environs/tags"
"github.com/juju/juju/state"
"github.com/juju/juju/storage"
)
Expand Down Expand Up @@ -179,3 +181,34 @@ func volumeAttachmentDevicePath(
}
return "", errNoDevicePath
}

// MaybeAssignedStorageInstance calls the provided function to get a
// StorageTag, and returns the corresponding state.StorageInstance if
// it didn't return an errors.IsNotAssigned error, or nil if it did.
func MaybeAssignedStorageInstance(
getTag func() (names.StorageTag, error),
getStorageInstance func(names.StorageTag) (state.StorageInstance, error),
) (state.StorageInstance, error) {
tag, err := getTag()
if err == nil {
return getStorageInstance(tag)
} else if errors.IsNotAssigned(err) {
return nil, nil
}
return nil, errors.Trace(err)
}

// storageTags returns the tags that should be set on a volume or filesystem,
// if the provider supports them.
func storageTags(
storageInstance state.StorageInstance,
cfg *config.Config,
) (map[string]string, error) {
uuid, _ := cfg.UUID()
storageTags := tags.ResourceTags(names.NewEnvironTag(uuid), cfg)
if storageInstance != nil {
storageTags[tags.JujuStorageInstance] = storageInstance.Tag().Id()
storageTags[tags.JujuStorageOwner] = storageInstance.Owner().Id()
}
return storageTags, nil
}
23 changes: 23 additions & 0 deletions apiserver/common/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package common_test

import (
"github.com/juju/juju/state"
"github.com/juju/names"
)

type fakeStorageInstance struct {
state.StorageInstance
tag names.StorageTag
owner names.Tag
}

func (i *fakeStorageInstance) Tag() names.Tag {
return i.tag
}

func (i *fakeStorageInstance) Owner() names.Tag {
return i.owner
}
14 changes: 13 additions & 1 deletion apiserver/common/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/juju/names"

"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/state"
"github.com/juju/juju/storage"
"github.com/juju/juju/storage/poolmanager"
Expand All @@ -28,7 +29,12 @@ func IsVolumeAlreadyProvisioned(err error) bool {
}

// VolumeParams returns the parameters for creating the given volume.
func VolumeParams(v state.Volume, poolManager poolmanager.PoolManager) (params.VolumeParams, error) {
func VolumeParams(
v state.Volume,
storageInstance state.StorageInstance,
environConfig *config.Config,
poolManager poolmanager.PoolManager,
) (params.VolumeParams, error) {
stateVolumeParams, ok := v.Params()
if !ok {
err := &volumeAlreadyProvisionedError{fmt.Errorf(
Expand All @@ -37,6 +43,11 @@ func VolumeParams(v state.Volume, poolManager poolmanager.PoolManager) (params.V
return params.VolumeParams{}, err
}

volumeTags, err := storageTags(storageInstance, environConfig)
if err != nil {
return params.VolumeParams{}, errors.Annotate(err, "computing storage tags")
}

providerType, cfg, err := StoragePoolConfig(stateVolumeParams.Pool, poolManager)
if err != nil {
return params.VolumeParams{}, errors.Trace(err)
Expand All @@ -46,6 +57,7 @@ func VolumeParams(v state.Volume, poolManager poolmanager.PoolManager) (params.V
stateVolumeParams.Size,
string(providerType),
cfg.Attrs(),
volumeTags,
nil, // attachment params set by the caller
}, nil
}
Expand Down
46 changes: 44 additions & 2 deletions apiserver/common/volumes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (

"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/environs/tags"
"github.com/juju/juju/state"
"github.com/juju/juju/storage"
"github.com/juju/juju/storage/poolmanager"
"github.com/juju/juju/testing"
)

type volumesSuite struct{}
Expand All @@ -39,7 +41,12 @@ func (v *fakeVolume) Params() (state.VolumeParams, bool) {

func (*volumesSuite) TestVolumeParamsAlreadyProvisioned(c *gc.C) {
tag := names.NewVolumeTag("100")
_, err := common.VolumeParams(&fakeVolume{tag: tag, provisioned: true}, nil)
_, err := common.VolumeParams(
&fakeVolume{tag: tag, provisioned: true},
nil, // StorageInstance
testing.EnvironConfig(c),
nil, // PoolManager
)
c.Assert(err, jc.Satisfies, common.IsVolumeAlreadyProvisioned)
}

Expand All @@ -53,11 +60,46 @@ func (pm *fakePoolManager) Get(name string) (*storage.Config, error) {

func (*volumesSuite) TestVolumeParams(c *gc.C) {
tag := names.NewVolumeTag("100")
p, err := common.VolumeParams(&fakeVolume{tag: tag}, &fakePoolManager{})
p, err := common.VolumeParams(
&fakeVolume{tag: tag},
nil, // StorageInstance
testing.CustomEnvironConfig(c, testing.Attrs{
"resource-tags": "a=b c=",
}),
&fakePoolManager{},
)
c.Assert(err, jc.ErrorIsNil)
c.Assert(p, jc.DeepEquals, params.VolumeParams{
VolumeTag: "volume-100",
Provider: "loop",
Size: 1024,
Tags: map[string]string{
tags.JujuEnv: testing.EnvironmentTag.Id(),
"a": "b",
"c": "",
},
})
}

func (*volumesSuite) TestVolumeParamsStorageTags(c *gc.C) {
volumeTag := names.NewVolumeTag("100")
storageTag := names.NewStorageTag("mystore/0")
unitTag := names.NewUnitTag("mysql/123")
p, err := common.VolumeParams(
&fakeVolume{tag: volumeTag},
&fakeStorageInstance{tag: storageTag, owner: unitTag},
testing.CustomEnvironConfig(c, nil),
&fakePoolManager{},
)
c.Assert(err, jc.ErrorIsNil)
c.Assert(p, jc.DeepEquals, params.VolumeParams{
VolumeTag: "volume-100",
Provider: "loop",
Size: 1024,
Tags: map[string]string{
tags.JujuEnv: testing.EnvironmentTag.Id(),
tags.JujuStorageInstance: "mystore/0",
tags.JujuStorageOwner: "mysql/123",
},
})
}
1 change: 1 addition & 0 deletions apiserver/params/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ type ProvisioningInfo struct {
Networks []string
Jobs []multiwatcher.MachineJob
Volumes []VolumeParams
Tags map[string]string
}

// ProvisioningInfoResult holds machine provisioning info or an error.
Expand Down
2 changes: 2 additions & 0 deletions apiserver/params/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ type VolumeParams struct {
Size uint64 `json:"size"`
Provider string `json:"provider"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
Attachment *VolumeAttachmentParams `json:"attachment,omitempty"`
}

Expand Down Expand Up @@ -323,6 +324,7 @@ type FilesystemParams struct {
Size uint64 `json:"size"`
Provider string `json:"provider"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
Attachment *FilesystemAttachmentParams `json:"attachment,omitempty"`
}

Expand Down
54 changes: 52 additions & 2 deletions apiserver/provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package provisioner

import (
"fmt"
"sort"
"strings"

"github.com/juju/errors"
"github.com/juju/loggo"
Expand All @@ -13,9 +15,11 @@ import (

"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/cloudconfig/instancecfg"
"github.com/juju/juju/constraints"
"github.com/juju/juju/container"
"github.com/juju/juju/environs"
"github.com/juju/juju/environs/tags"
"github.com/juju/juju/instance"
"github.com/juju/juju/network"
"github.com/juju/juju/provider"
Expand All @@ -30,7 +34,7 @@ import (
var logger = loggo.GetLogger("juju.apiserver.provisioner")

func init() {
common.RegisterStandardFacade("Provisioner", 0, NewProvisionerAPI)
common.RegisterStandardFacade("Provisioner", 1, NewProvisionerAPI)
}

// ProvisionerAPI provides access to the Provisioner API facade.
Expand Down Expand Up @@ -410,13 +414,18 @@ func (p *ProvisionerAPI) getProvisioningInfo(m *state.Machine) (*params.Provisio
for _, job := range m.Jobs() {
jobs = append(jobs, job.ToParams())
}
tags, err := p.machineTags(m, jobs)
if err != nil {
return nil, errors.Trace(err)
}
return &params.ProvisioningInfo{
Constraints: cons,
Series: m.Series(),
Placement: m.Placement(),
Networks: networks,
Jobs: jobs,
Volumes: volumes,
Tags: tags,
}, nil
}

Expand Down Expand Up @@ -544,6 +553,10 @@ func (p *ProvisionerAPI) machineVolumeParams(m *state.Machine) ([]params.VolumeP
if len(volumeAttachments) == 0 {
return nil, nil
}
envConfig, err := p.st.EnvironConfig()
if err != nil {
return nil, err
}
poolManager := poolmanager.New(state.NewStateSettings(p.st))
allVolumeParams := make([]params.VolumeParams, 0, len(volumeAttachments))
for _, volumeAttachment := range volumeAttachments {
Expand All @@ -552,7 +565,13 @@ func (p *ProvisionerAPI) machineVolumeParams(m *state.Machine) ([]params.VolumeP
if err != nil {
return nil, errors.Annotatef(err, "getting volume %q", volumeTag.Id())
}
volumeParams, err := common.VolumeParams(volume, poolManager)
storageInstance, err := common.MaybeAssignedStorageInstance(
volume.StorageInstance, p.st.StorageInstance,
)
if err != nil {
return nil, errors.Annotatef(err, "getting volume %q storage instance", volumeTag.Id())
}
volumeParams, err := common.VolumeParams(volume, storageInstance, envConfig, poolManager)
if common.IsVolumeAlreadyProvisioned(err) {
// Already provisioned, so must be dynamic.
continue
Expand Down Expand Up @@ -1263,3 +1282,34 @@ func (p *ProvisionerAPI) createOrFetchStateSubnet(subnetInfo network.SubnetInfo)
}
return subnet, nil
}

// machineTags returns machine-specific tags to set on the instance.
func (p *ProvisionerAPI) machineTags(m *state.Machine, jobs []multiwatcher.MachineJob) (map[string]string, error) {
// Names of all units deployed to the machine.
//
// TODO(axw) 2015-06-02 #1461358
// We need a worker that periodically updates
// instance tags with current deployment info.
units, err := m.Units()
if err != nil {
return nil, errors.Trace(err)
}
unitNames := make([]string, 0, len(units))
for _, unit := range units {
if !unit.IsPrincipal() {
continue
}
unitNames = append(unitNames, unit.Name())
}
sort.Strings(unitNames)

cfg, err := p.st.EnvironConfig()
if err != nil {
return nil, errors.Trace(err)
}
machineTags := instancecfg.InstanceTags(cfg, jobs)
if len(unitNames) > 0 {
machineTags[tags.JujuUnitsDeployed] = strings.Join(unitNames, " ")
}
return machineTags, nil
}
Loading

0 comments on commit 5cb1112

Please sign in to comment.