github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/maas/instance.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/gomaasapi" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/core/instance" 15 "github.com/juju/juju/core/status" 16 "github.com/juju/juju/environs/context" 17 "github.com/juju/juju/environs/instances" 18 "github.com/juju/juju/network" 19 "github.com/juju/juju/provider/common" 20 "github.com/juju/juju/storage" 21 ) 22 23 type maasInstance interface { 24 instances.Instance 25 zone() (string, error) 26 hostname() (string, error) 27 hardwareCharacteristics() (*instance.HardwareCharacteristics, error) 28 volumes(names.MachineTag, []names.VolumeTag) ([]storage.Volume, []storage.VolumeAttachment, error) 29 } 30 31 type maas1Instance struct { 32 maasObject *gomaasapi.MAASObject 33 environ *maasEnviron 34 statusGetter func(context.ProviderCallContext, instance.Id) (string, string) 35 } 36 37 var _ maasInstance = (*maas1Instance)(nil) 38 39 func (mi *maas1Instance) String() string { 40 hostname, err := mi.hostname() 41 if err != nil { 42 // This is meant to be impossible, but be paranoid. 43 logger.Errorf("unable to detect MAAS instance hostname: %q", err) 44 hostname = fmt.Sprintf("<DNSName failed: %q>", err) 45 } 46 return fmt.Sprintf("%s:%s", hostname, mi.Id()) 47 } 48 49 func (mi *maas1Instance) Id() instance.Id { 50 // Note: URI excludes the hostname 51 return instance.Id(mi.maasObject.URI().String()) 52 } 53 54 func normalizeStatus(statusMsg string) string { 55 return strings.ToLower(strings.TrimSpace(statusMsg)) 56 } 57 58 func convertInstanceStatus(statusMsg, substatus string, id instance.Id) instance.Status { 59 maasInstanceStatus := status.Empty 60 switch normalizeStatus(statusMsg) { 61 case "": 62 logger.Debugf("unable to obtain status of instance %s", id) 63 statusMsg = "error in getting status" 64 case "deployed": 65 maasInstanceStatus = status.Running 66 case "deploying": 67 maasInstanceStatus = status.Allocating 68 if substatus != "" { 69 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 70 } 71 case "failed deployment": 72 maasInstanceStatus = status.ProvisioningError 73 if substatus != "" { 74 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 75 } 76 default: 77 maasInstanceStatus = status.Empty 78 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 79 } 80 return instance.Status{ 81 Status: maasInstanceStatus, 82 Message: statusMsg, 83 } 84 } 85 86 // Status returns a juju status based on the maas instance returned 87 // status message. 88 func (mi *maas1Instance) Status(ctx context.ProviderCallContext) instance.Status { 89 statusMsg, substatus := mi.statusGetter(ctx, mi.Id()) 90 return convertInstanceStatus(statusMsg, substatus, mi.Id()) 91 } 92 93 func (mi *maas1Instance) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) { 94 interfaceAddresses, err := mi.interfaceAddresses(ctx) 95 if err != nil { 96 return nil, errors.Annotate(err, "getting node interfaces") 97 } 98 99 logger.Debugf("instance %q has interface addresses: %+v", mi.Id(), interfaceAddresses) 100 return interfaceAddresses, nil 101 } 102 103 var refreshMAASObject = func(maasObject *gomaasapi.MAASObject) (gomaasapi.MAASObject, error) { 104 // Defined like this to allow patching in tests to overcome limitations of 105 // gomaasapi's test server. 106 return maasObject.Get() 107 } 108 109 // interfaceAddresses fetches a fresh copy of the node details from MAAS and 110 // extracts all addresses from the node's interfaces. Returns an error 111 // satisfying errors.IsNotSupported() if MAAS API does not report interfaces 112 // information. 113 func (mi *maas1Instance) interfaceAddresses(ctx context.ProviderCallContext) ([]network.Address, error) { 114 // Fetch a fresh copy of the instance JSON first. 115 obj, err := refreshMAASObject(mi.maasObject) 116 if err != nil { 117 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 118 return nil, errors.Annotate(err, "getting instance details") 119 } 120 121 subnetsMap, err := mi.environ.subnetToSpaceIds(ctx) 122 if err != nil { 123 return nil, errors.Trace(err) 124 } 125 // Get all the interface details and extract the addresses. 126 interfaces, err := maasObjectNetworkInterfaces(ctx, &obj, subnetsMap) 127 if err != nil { 128 return nil, errors.Trace(err) 129 } 130 131 var addresses []network.Address 132 for _, iface := range interfaces { 133 if iface.Address.Value != "" { 134 logger.Debugf("found address %q on interface %q", iface.Address, iface.InterfaceName) 135 addresses = append(addresses, iface.Address) 136 } else { 137 logger.Infof("no address found on interface %q", iface.InterfaceName) 138 } 139 } 140 return addresses, nil 141 } 142 143 func (mi *maas1Instance) architecture() (arch, subarch string, err error) { 144 // MAAS may return an architecture of the form, for example, 145 // "amd64/generic"; we only care about the major part. 146 arch, err = mi.maasObject.GetField("architecture") 147 if err != nil { 148 return "", "", err 149 } 150 parts := strings.SplitN(arch, "/", 2) 151 arch = parts[0] 152 if len(parts) == 2 { 153 subarch = parts[1] 154 } 155 return arch, subarch, nil 156 } 157 158 func (mi *maas1Instance) zone() (string, error) { 159 // TODO (anastasiamac 2016-03-31) 160 // This code is needed until gomaasapi testing code is 161 // updated to align with MAAS. 162 // Currently, "zone" property is still treated as field 163 // by gomaasi infrastructure and is searched for 164 // using matchField(node, "zone", zoneName) instead of 165 // getMap. 166 // @see gomaasapi/testservice.go#findFreeNode 167 // bug https://bugs.launchpad.net/gomaasapi/+bug/1563631 168 zone, fieldErr := mi.maasObject.GetField("zone") 169 if fieldErr == nil && zone != "" { 170 return zone, nil 171 } 172 173 obj := mi.maasObject.GetMap()["zone"] 174 if obj.IsNil() { 175 return "", errors.New("zone property not set on maas") 176 } 177 zoneMap, err := obj.GetMap() 178 if err != nil { 179 return "", errors.New("zone property is not an expected type") 180 } 181 nameObj, ok := zoneMap["name"] 182 if !ok { 183 return "", errors.New("zone property is not set correctly: name is missing") 184 } 185 str, err := nameObj.GetString() 186 if err != nil { 187 return "", err 188 } 189 return str, nil 190 } 191 192 func (mi *maas1Instance) cpuCount() (uint64, error) { 193 count, err := mi.maasObject.GetMap()["cpu_count"].GetFloat64() 194 if err != nil { 195 return 0, err 196 } 197 return uint64(count), nil 198 } 199 200 func (mi *maas1Instance) memory() (uint64, error) { 201 mem, err := mi.maasObject.GetMap()["memory"].GetFloat64() 202 if err != nil { 203 return 0, err 204 } 205 return uint64(mem), nil 206 } 207 208 func (mi *maas1Instance) tagNames() ([]string, error) { 209 obj := mi.maasObject.GetMap()["tag_names"] 210 if obj.IsNil() { 211 return nil, errors.NotFoundf("tag_names") 212 } 213 array, err := obj.GetArray() 214 if err != nil { 215 return nil, err 216 } 217 tags := make([]string, len(array)) 218 for i, obj := range array { 219 tag, err := obj.GetString() 220 if err != nil { 221 return nil, err 222 } 223 tags[i] = tag 224 } 225 return tags, nil 226 } 227 228 func (mi *maas1Instance) hardwareCharacteristics() (*instance.HardwareCharacteristics, error) { 229 nodeArch, _, err := mi.architecture() 230 if err != nil { 231 return nil, errors.Annotate(err, "error determining architecture") 232 } 233 nodeCpuCount, err := mi.cpuCount() 234 if err != nil { 235 return nil, errors.Annotate(err, "error determining cpu count") 236 } 237 nodeMemoryMB, err := mi.memory() 238 if err != nil { 239 return nil, errors.Annotate(err, "error determining available memory") 240 } 241 zone, err := mi.zone() 242 if err != nil { 243 return nil, errors.Annotate(err, "error determining availability zone") 244 } 245 hc := &instance.HardwareCharacteristics{ 246 Arch: &nodeArch, 247 CpuCores: &nodeCpuCount, 248 Mem: &nodeMemoryMB, 249 AvailabilityZone: &zone, 250 } 251 nodeTags, err := mi.tagNames() 252 if err != nil && !errors.IsNotFound(err) { 253 return nil, errors.Annotate(err, "error determining tag names") 254 } 255 if len(nodeTags) > 0 { 256 hc.Tags = &nodeTags 257 } 258 return hc, nil 259 } 260 261 func (mi *maas1Instance) hostname() (string, error) { 262 // A MAAS instance has its DNS name immediately. 263 return mi.maasObject.GetField("hostname") 264 }