github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/cloud.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/utils/set" 9 "gopkg.in/juju/names.v2" 10 "gopkg.in/mgo.v2" 11 "gopkg.in/mgo.v2/txn" 12 13 "github.com/juju/juju/cloud" 14 ) 15 16 // cloudGlobalKey returns the global database key for the specified cloud. 17 func cloudGlobalKey(name string) string { 18 return "cloud#" + name 19 } 20 21 // cloudDoc records information about the cloud that the controller operates in. 22 type cloudDoc struct { 23 DocID string `bson:"_id"` 24 Name string `bson:"name"` 25 Type string `bson:"type"` 26 AuthTypes []string `bson:"auth-types"` 27 Endpoint string `bson:"endpoint"` 28 IdentityEndpoint string `bson:"identity-endpoint,omitempty"` 29 StorageEndpoint string `bson:"storage-endpoint,omitempty"` 30 Regions map[string]cloudRegionSubdoc `bson:"regions,omitempty"` 31 } 32 33 // cloudRegionSubdoc records information about cloud regions. 34 type cloudRegionSubdoc struct { 35 Endpoint string `bson:"endpoint,omitempty"` 36 IdentityEndpoint string `bson:"identity-endpoint,omitempty"` 37 StorageEndpoint string `bson:"storage-endpoint,omitempty"` 38 } 39 40 // createCloudOp returns a list of txn.Ops that will initialize 41 // the cloud definition for the controller. 42 func createCloudOp(cloud cloud.Cloud, cloudName string) txn.Op { 43 authTypes := make([]string, len(cloud.AuthTypes)) 44 for i, authType := range cloud.AuthTypes { 45 authTypes[i] = string(authType) 46 } 47 regions := make(map[string]cloudRegionSubdoc) 48 for _, region := range cloud.Regions { 49 regions[region.Name] = cloudRegionSubdoc{ 50 region.Endpoint, 51 region.IdentityEndpoint, 52 region.StorageEndpoint, 53 } 54 } 55 return txn.Op{ 56 C: cloudsC, 57 Id: cloudName, 58 Assert: txn.DocMissing, 59 Insert: &cloudDoc{ 60 Name: cloudName, 61 Type: cloud.Type, 62 AuthTypes: authTypes, 63 Endpoint: cloud.Endpoint, 64 IdentityEndpoint: cloud.IdentityEndpoint, 65 StorageEndpoint: cloud.StorageEndpoint, 66 Regions: regions, 67 }, 68 } 69 } 70 71 func (d cloudDoc) toCloud() cloud.Cloud { 72 authTypes := make([]cloud.AuthType, len(d.AuthTypes)) 73 for i, authType := range d.AuthTypes { 74 authTypes[i] = cloud.AuthType(authType) 75 } 76 regionNames := make(set.Strings) 77 for name := range d.Regions { 78 regionNames.Add(name) 79 } 80 regions := make([]cloud.Region, len(d.Regions)) 81 for i, name := range regionNames.SortedValues() { 82 region := d.Regions[name] 83 regions[i] = cloud.Region{ 84 name, 85 region.Endpoint, 86 region.IdentityEndpoint, 87 region.StorageEndpoint, 88 } 89 } 90 return cloud.Cloud{ 91 Type: d.Type, 92 AuthTypes: authTypes, 93 Endpoint: d.Endpoint, 94 IdentityEndpoint: d.IdentityEndpoint, 95 StorageEndpoint: d.StorageEndpoint, 96 Regions: regions, 97 } 98 } 99 100 // Clouds returns the definitions for all clouds in the controller. 101 func (st *State) Clouds() (map[names.CloudTag]cloud.Cloud, error) { 102 coll, cleanup := st.getCollection(cloudsC) 103 defer cleanup() 104 105 var doc cloudDoc 106 clouds := make(map[names.CloudTag]cloud.Cloud) 107 iter := coll.Find(nil).Iter() 108 for iter.Next(&doc) { 109 clouds[names.NewCloudTag(doc.Name)] = doc.toCloud() 110 } 111 if err := iter.Err(); err != nil { 112 return nil, errors.Annotate(err, "getting clouds") 113 } 114 return clouds, nil 115 } 116 117 // Cloud returns the controller's cloud definition. 118 func (st *State) Cloud(name string) (cloud.Cloud, error) { 119 coll, cleanup := st.getCollection(cloudsC) 120 defer cleanup() 121 122 var doc cloudDoc 123 err := coll.FindId(name).One(&doc) 124 if err == mgo.ErrNotFound { 125 return cloud.Cloud{}, errors.NotFoundf("cloud %q", name) 126 } 127 if err != nil { 128 return cloud.Cloud{}, errors.Annotatef(err, "cannot get cloud %q", name) 129 } 130 return doc.toCloud(), nil 131 } 132 133 // AddCloud creates a cloud with the given name and details. 134 // Note that the Config is deliberately ignored - it's only 135 // relevant when bootstrapping. 136 func (st *State) AddCloud(name string, c cloud.Cloud) error { 137 if err := validateCloud(c); err != nil { 138 return errors.Annotate(err, "invalid cloud") 139 } 140 ops := []txn.Op{createCloudOp(c, name)} 141 if err := st.runTransaction(ops); err != nil { 142 if err == txn.ErrAborted { 143 err = errors.AlreadyExistsf("cloud %q", name) 144 } 145 return err 146 } 147 return nil 148 } 149 150 // validateCloud checks that the supplied cloud is valid. 151 func validateCloud(cloud cloud.Cloud) error { 152 if cloud.Type == "" { 153 return errors.NotValidf("empty Type") 154 } 155 if len(cloud.AuthTypes) == 0 { 156 return errors.NotValidf("empty auth-types") 157 } 158 // TODO(axw) we should ensure that the cloud auth-types is a subset 159 // of the auth-types supported by the provider. To do that, we'll 160 // need a new "policy". 161 return nil 162 } 163 164 // regionSettingsGlobalKey concatenates the cloud a hash and the region string. 165 func regionSettingsGlobalKey(cloud, region string) string { 166 return cloud + "#" + region 167 }