├── .eslintrc ├── .gitignore ├── .gitmodules ├── .kthxbai ├── .nycrc ├── CHANGES.md ├── Jenkinsfile ├── LICENSE ├── Makefile ├── README.md ├── TODO.md ├── bin └── sdcadm ├── docs ├── config.md ├── index.md ├── link-patterns.txt ├── post-setup.md └── update.md ├── etc ├── defaults.json ├── sample-packages.json └── setup │ └── user-script ├── lib ├── channel.js ├── cli │ ├── do_add_new_agent_svcs.js │ ├── do_avail.js │ ├── do_check_config.js │ ├── do_check_health.js │ ├── do_completion.js │ ├── do_create.js │ ├── do_fix_core_vm_resolvers.js │ ├── do_info.js │ ├── do_install_docker_cert.js │ ├── do_instances.js │ ├── do_nfs_volumes.js │ ├── do_remove_ca.js │ ├── do_rollback.js │ ├── do_self_update.js │ ├── do_services.js │ ├── do_update.js │ ├── do_update_agents.js │ ├── do_update_docker.js │ ├── do_update_gz_tools.js │ ├── do_update_other.js │ ├── experimental.js │ ├── index.js │ └── ui.js ├── common.js ├── dc-maint.js ├── default-fabric.js ├── errors.js ├── logging.js ├── manta.js ├── platform.js ├── post-setup │ ├── cloudapi.js │ ├── cmon.js │ ├── cns.js │ ├── common-external-nics.js │ ├── dev-headnode-prov.js │ ├── dev-sample-data.js │ ├── do_manta.js │ ├── docker.js │ ├── fabrics.js │ ├── firewall-logger-agent.js │ ├── grafana.js │ ├── ha-binder.js │ ├── ha-manatee.js │ ├── index.js │ ├── kbmapi.js │ ├── logarchiver.js │ ├── prometheus.js │ ├── underlay-nics.js │ └── volapi.js ├── procedures │ ├── add-agent-service.js │ ├── add-allow-transfer.js │ ├── add-service.js │ ├── create-service-instance-v1.js │ ├── download-images.js │ ├── ensure-manta-deployment-gz-links.js │ ├── ensure-nic-on-instances.js │ ├── index.js │ ├── procedure.js │ ├── remove-services.js │ ├── set-mantav2-migration-metadata.js │ ├── shared.js │ ├── update-agent-v1.js │ ├── update-binder-v2.js │ ├── update-dockerlogger.js │ ├── update-manatee-v2.js │ ├── update-moray-v2.js │ ├── update-sapi-v2.js │ ├── update-single-headnode-imgapi.js │ ├── update-single-hn-sapi-v1.js │ └── update-stateless-services-v1.js ├── sdcadm.js ├── steps │ ├── README.md │ ├── binder.js │ ├── dnsdomain.js │ ├── images.js │ ├── index.js │ ├── noRabbit.js │ ├── sapi.js │ ├── servers.js │ ├── updateVmSize.js │ ├── usbkey.js │ └── zookeeper.js ├── svcadm.js ├── ur.js └── vmadm.js ├── man └── man1 │ └── sdcadm.1.ronn ├── package-lock.json ├── package.json ├── smf ├── manifests │ └── sdcadm-setup.xml └── method │ └── sdcadm-setup ├── test ├── available.test.js ├── channel.test.js ├── check-config.test.js ├── check-health.test.js ├── common.js ├── common.test.js ├── create.test.js ├── dc-maint.test.js ├── experimental.test.js ├── help.test.js ├── instances.test.js ├── manatee.test.js ├── platform.test.js ├── post-setup-ha-binder.test.js ├── post-setup.test.js ├── procedures-shared.test.js ├── rollback.test.js ├── runtests ├── sdcadm.test.js ├── self-update.test.js ├── services.test.js ├── shared.js ├── unit │ ├── cli │ │ ├── do_platform.test.js │ │ └── do_update_agents.test.js │ ├── steps │ │ ├── servers.test.js │ │ └── usbkey.test.js │ └── testutil.js ├── update-gz-tools.test.js └── update.test.js └── tools ├── gen-sample-packages.py ├── install-sdcadm.sh ├── md5sum-for-smartos-gz ├── mk-shar ├── rotate-logs.sh └── rsync-to /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ "joyent" ], 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:joyent/style", 6 | "plugin:joyent/lint" 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 6, 10 | "sourceType": "script", 11 | "ecmaFeatures": { 12 | } 13 | }, 14 | "env": { 15 | "node": true, 16 | "es6": true 17 | }, 18 | "rules": { 19 | // Local rule configuration 20 | "no-unused-vars": [ 21 | "error", 22 | { 23 | // Track all unused identifiers 24 | "vars": "all", 25 | "args": "all", 26 | "caughtErrors": "all", 27 | // Don't warn on args that start with _, res or req. 28 | // Added stdout and stderr to the standard joyent set. 29 | "argsIgnorePattern": "^(_|res|req|stdout|stderr)", 30 | // Don't warn on catch or var identifiers that start with _ 31 | "caughtIgnorePattern": "^_", 32 | "varsIgnorePattern": "^_" 33 | } 34 | ], 35 | "max-len": [ 36 | "error", 37 | 80, 38 | { 39 | "tabWidth": 8, 40 | "ignoreComments": false, 41 | "ignoreTrailingComments": false, 42 | "ignoreUrls": true 43 | } 44 | ], 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.vim 2 | /bits 3 | /build 4 | /docs/*.html 5 | /docs/*.json 6 | /make_stamps 7 | /node_modules 8 | /npm-debug.log 9 | /sdcadm-*.sh 10 | /tmp 11 | /etc/sdcadm.completion 12 | man/man1/sdcadm.1 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/restdown"] 2 | path = deps/restdown 3 | url = https://github.com/TritonDataCenter/restdown.git 4 | [submodule "deps/restdown-brand-remora"] 5 | path = deps/restdown-brand-remora 6 | url = https://github.com/TritonDataCenter/restdown-brand-remora.git 7 | [submodule "deps/eng"] 8 | path = deps/eng 9 | url = https://github.com/TritonDataCenter/eng.git 10 | -------------------------------------------------------------------------------- /.kthxbai: -------------------------------------------------------------------------------- 1 | # kthxbai config file (https://github.com/trentm/node-kthxbai) 2 | 3 | # +pragma: scrub-package-json 4 | 5 | # Default patterns 6 | **/AUTHORS 7 | **/CONTRIBUTORS 8 | **/README* 9 | **/Readme* 10 | **/readme* 11 | **/TODO* 12 | **/CHANGES.md 13 | **/History.md 14 | **/example 15 | **/examples 16 | **/samples 17 | **/test 18 | **/tests 19 | **/tst 20 | **/doc 21 | **/docs 22 | **/man 23 | **/.travis.yml 24 | 25 | # Custom patterns 26 | **/.dir-locals.el 27 | **/.gitmodules 28 | **/.npmignore 29 | **/.jshintrc 30 | **/docs 31 | **/Makefile 32 | **/benchmark 33 | 34 | # sdc-clients: Only lib/sapi.js is used. 35 | # TODO: Not sure all of these are safely removable. 36 | sdc-clients/tools 37 | sdc-clients/lib/*.javascript 38 | sdc-clients/lib/{dsapi,usageapi}.js 39 | 40 | # dtrace-provider 41 | **/dtrace-provider/build/{config.gypi,*.target.mk,gyp-flock-tool,binding.Makefile} 42 | **/dtrace-provider/build/Release/{.deps,obj.target,linker.lock} 43 | **/dtrace-provider/!(build|package.json|dtrace-provider.js|LICENSE) 44 | **/dtrace-provider/package.json.rej 45 | 46 | # verror 47 | **/verror/Makefile* 48 | **/verror/jsl.node.conf 49 | **/verror/experiments 50 | 51 | # extsprintf 52 | **/extsprintf/Makefile* 53 | **/extsprintf/jsl.node.conf 54 | 55 | # cmdln & dashdash 56 | **/cmdln/tools 57 | **/dashdash/tools 58 | 59 | # restify: our usage here is limited 60 | **/restify/node_modules/spdy 61 | 62 | # mkdirp 63 | **/mkdirp/node_modules/minimist 64 | **/mkdirp/bin 65 | **/.bin/mkdirp 66 | 67 | # bunyan (we don't need bunyan's optional 'mv' module usage) 68 | **/bunyan/node_modules/mv 69 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": [ 4 | "bin/**/*.js", 5 | "cmd/**/*.js", 6 | "lib/**/*.js" 7 | ], 8 | "report-dir": "./build/coverage", 9 | "temp-directory": "./build/nyc_output", 10 | "reporter": [ 11 | "cobertura", 12 | "clover", 13 | "json", 14 | "json-summary", 15 | "html", 16 | "text" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2021 Joyent, Inc. 9 | */ 10 | 11 | @Library('jenkins-joylib@v1.0.8') _ 12 | 13 | pipeline { 14 | 15 | agent { 16 | label joyCommonLabels(image_ver: '18.4.0') 17 | } 18 | 19 | options { 20 | buildDiscarder(logRotator(numToKeepStr: '30')) 21 | timestamps() 22 | } 23 | 24 | stages { 25 | stage('check') { 26 | steps{ 27 | sh('make check') 28 | } 29 | } 30 | stage('test') { 31 | steps{ 32 | sh('make all test-unit') 33 | } 34 | } 35 | // avoid bundling devDependencies 36 | stage('re-clean') { 37 | steps { 38 | sh(''' 39 | git clean -fdx 40 | git reset --hard 41 | ''') 42 | } 43 | } 44 | stage('build image and upload') { 45 | steps { 46 | sh(''' 47 | set -o errexit 48 | set -o pipefail 49 | 50 | export ENGBLD_BITS_UPLOAD_IMGAPI=true 51 | make print-BRANCH print-STAMP all release publish bits-upload 52 | ''') 53 | } 54 | } 55 | } 56 | 57 | post { 58 | always { 59 | joySlackNotifications() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2019, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile for sdcadm 13 | # 14 | 15 | # 16 | # Vars, Tools, Files, Flags 17 | # 18 | NAME := sdcadm 19 | DOC_FILES = index.md config.md update.md post-setup.md 20 | EXTRA_DOC_DEPS += deps/restdown-brand-remora/.git 21 | RESTDOWN_FLAGS = --brand-dir=deps/restdown-brand-remora 22 | JS_FILES := $(shell find lib test -name '*.js' | grep -v '/tmp/') 23 | ESLINT_FILES = $(JS_FILES) 24 | BUILD = ./build 25 | CLEAN_FILES += ./node_modules ./build/sdcadm-*.sh ./build/sdcadm-*.imgmanifest ./build/shar-image ./man/man1/sdcadm.1 ./etc/sdcadm.completion $(BUILD) 26 | TAP_EXEC := ./node_modules/.bin/tap 27 | TEST_UNIT_JOBS ?= 4 28 | 29 | NODE_PREBUILT_VERSION=v6.17.0 30 | ifeq ($(shell uname -s),SunOS) 31 | NODE_PREBUILT_TAG=gz 32 | # minimal-64-lts 18.4.0 33 | NODE_PREBUILT_IMAGE=c2c31b00-1d60-11e9-9a77-ff9f06554b0f 34 | endif 35 | 36 | ENGBLD_REQUIRE := $(shell git submodule update --init deps/eng) 37 | include ./deps/eng/tools/mk/Makefile.defs 38 | TOP ?= $(error Unable to access eng.git submodule Makefiles.) 39 | 40 | ifeq ($(shell uname -s),SunOS) 41 | include ./deps/eng/tools/mk/Makefile.node_prebuilt.defs 42 | else 43 | # Good enough for non-SmartOS dev. 44 | NPM=npm 45 | NODE=node 46 | NPM_EXEC=$(shell which npm) 47 | NODE_EXEC=$(shell which node) 48 | endif 49 | 50 | 51 | # 52 | # Targets 53 | # 54 | .PHONY: all 55 | all: | $(NPM_EXEC) 56 | MAKE_OVERRIDES='CTFCONVERT=/bin/true CTFMERGE=/bin/true' $(NPM) install 57 | $(NODE) ./node_modules/.bin/kthxbai || true # work around trentm/node-kthxbai#1 58 | $(NODE) ./node_modules/.bin/kthxbai 59 | rm -rf ./node_modules/.bin/kthxbai ./node_modules/kthxbai 60 | 61 | $(BUILD): 62 | mkdir $@ 63 | 64 | .PHONY: shar 65 | shar: 66 | ./tools/mk-shar -o $(TOP)/build -s $(STAMP) 67 | 68 | .PHONY: test 69 | test: 70 | ./test/runtests 71 | 72 | .PHONY: test-unit 73 | test-unit: | $(TAP_EXEC) $(BUILD) 74 | $(NODE) $(TAP_EXEC) --jobs=$(TEST_UNIT_JOBS) \ 75 | --output-file=$(BUILD)/test.unit.tap \ 76 | test/unit/**/*.test.js 77 | 78 | .PHONY: test-coverage-unit 79 | test-coverage-unit: | $(TAP_EXEC) $(BUILD) 80 | $(NODE) $(TAP_EXEC) --jobs=$(TEST_UNIT_JOBS) \ 81 | --output-file=$(BUILD)/test.unit.tap \ 82 | --coverage test/unit/**/*.test.js 83 | 84 | .PHONY: release 85 | release: all man completion shar 86 | 87 | .PHONY: publish 88 | publish: release 89 | mkdir -p $(ENGBLD_BITS_DIR)/$(NAME) 90 | cp \ 91 | $(TOP)/build/sdcadm-$(STAMP).sh \ 92 | $(TOP)/build/sdcadm-$(STAMP).imgmanifest \ 93 | $(ENGBLD_BITS_DIR)/$(NAME)/ 94 | 95 | .PHONY: dumpvar 96 | dumpvar: 97 | @if [[ -z "$(VAR)" ]]; then \ 98 | echo "error: set 'VAR' to dump a var"; \ 99 | exit 1; \ 100 | fi 101 | @echo "$(VAR) is '$($(VAR))'" 102 | 103 | # Ensure all version-carrying files have the same version. 104 | .PHONY: check-version 105 | check-version: 106 | @echo version is: $(shell json -f package.json version) 107 | @if [[ $$(json -f package.json version) != \ 108 | $$(awk '/^## / { print $$2; exit 0 }' CHANGES.md) ]]; then \ 109 | printf 'package.json version does not match CHANGES.md\n' >&2; \ 110 | exit 1; \ 111 | fi 112 | @echo Version check ok. 113 | 114 | check:: check-version 115 | 116 | 117 | .PHONY: man 118 | man: man/man1/sdcadm.1.ronn | $(NODE_EXEC) 119 | rm -f man/man1/sdcadm.1 120 | $(NODE_EXEC) ./node_modules/.bin/marked-man \ 121 | --input man/man1/sdcadm.1.ronn \ 122 | --date `git log -1 --pretty=format:%cd --date=short` \ 123 | --output man/man1/sdcadm.1 124 | chmod 444 man/man1/sdcadm.1 125 | 126 | .PHONY: completion 127 | completion: 128 | rm -f etc/sdcadm.completion 129 | ./bin/sdcadm completion > etc/sdcadm.completion 130 | 131 | include ./deps/eng/tools/mk/Makefile.deps 132 | ifeq ($(shell uname -s),SunOS) 133 | include ./deps/eng/tools/mk/Makefile.node_prebuilt.targ 134 | endif 135 | include ./deps/eng/tools/mk/Makefile.targ 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | # sdcadm 14 | 15 | This repository is part of the Triton DataCenter project. See the [contribution 16 | guidelines](https://github.com/TritonDataCenter/triton/blob/master/CONTRIBUTING.md) 17 | and general documentation at the main 18 | [Triton project](https://github.com/TritonDataCenter/triton) page. 19 | 20 | `sdcadm` is a tool that lives in the Triton headnode's GZ, for 21 | handling post-setup (i.e. setup steps after initial headnode setup), 22 | upgrades, listing of services and instances, health checks, and other setup 23 | duties. 24 | 25 | Please see the [index](./docs/index.md) for more details. 26 | 27 | 28 | # Current status 29 | 30 | While `sdcadm` is still under significant development, and is far from complete, 31 | it is currently the recommended way to update Triton. Signs of incompleteness 32 | are that sub-commands of `sdcadm experimental ...` are required as part of the 33 | upgrade process. 34 | 35 | # Triton post-setup with sdcadm 36 | 37 | The document [post-setup](docs/post-setup.md) details the required steps in 38 | order to configure Triton DataCenter for practical usage, like HA setup and the 39 | addition of services not installed by default. 40 | 41 | # Manage Triton upgrades with sdcadm 42 | 43 | The document [update](docs/update.md) provides a detailed description on how to 44 | proceed with the update of a given Triton DataCenter (just "Triton" for 45 | short) standup. 46 | 47 | # Man page 48 | 49 | The [sdcadm man page](man/man1/sdcadm.1.ronn) provides reference for every 50 | sdcadm subcommand. 51 | 52 | # Developer notes 53 | 54 | ## Updating sdcadm 55 | 56 | To update to bits you've built locally (with `make publish`), copy over 57 | `bits/sdcadm/` to your headnode, import them into your `imgapi` instance, 58 | then use the `-S` flag to `sdcadm self-update`: 59 | 60 | sdc-imgadm import -c none \ 61 | -f /tmp/sdcadm-mybranch-20190701T145750Z-gfcba035.sh 62 | -m /tmp/sdcadm-mybranch-20190701T145750Z-gfcba035.imgmanifest 63 | sdcadm self-update -S http://imgapi.mydc.example.com/ --latest 64 | 65 | ## Testing sdcadm 66 | 67 | This should only be done by developers, and only in dev or test environments. 68 | Tests will muck around with the sdc setup, doing terrible and unholy things to 69 | your data. 70 | 71 | Note that tests are expected to run on a fresh setup, since the test suite 72 | will go through all the `post-setup` subcommands. The installed sdcadm version 73 | must also be available on `https://updates.tritondatacenter.com/` for the 74 | `self-update` tests to pass. 75 | 76 | In order to run sdcadm tests, you'll first need to signal to the tests that 77 | you really do want them to run: 78 | 79 | touch /lib/sdc/.sdc-test-no-production-data 80 | 81 | After that, to run the tests themselves: 82 | 83 | /opt/smartdc/sdcadm/test/runtests 84 | 85 | The full battery of tests can take up to thirty minutes to run. To only run 86 | tests in a single file, instead of all test files, consider using the -f flag 87 | with the `runtests` command. For example, to run the tests in sdcadm.test.js: 88 | 89 | /opt/smartdc/sdcadm/test/runtests -f sdcadm.test.js 90 | 91 | ### Unit Tests 92 | 93 | `sdcadm` includes some unit tests. At this time the coverage is significantly 94 | less than the integration tests. You must still run them on a headnode. Unit 95 | tests can be run with: 96 | 97 | make test-unit 98 | 99 | Individual test files can be run with a command such as: 100 | 101 | ./node_modules/.bin/tap test/unit/foo.js 102 | 103 | `node-tap` includes several flags that may be useful while developing, such as 104 | only running suites that match a certain name. 105 | -------------------------------------------------------------------------------- /bin/sdcadm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2017, Joyent, Inc. 10 | # 11 | 12 | TOP=$(cd $(dirname $0)/../; pwd) 13 | if [[ -x "$TOP/build/node/bin/node" ]]; then 14 | # Dev layout. 15 | $TOP/build/node/bin/node --abort_on_uncaught_exception $TOP/lib/cli/index.js "$@" 16 | else 17 | /opt/smartdc/sdcadm/node/bin/node --abort_on_uncaught_exception /opt/smartdc/sdcadm/lib/cli/index.js "$@" 18 | fi 19 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: sdcadm (Administer a SDC standup) 3 | markdown2extras: tables, code-friendly, cuddled-lists, link-patterns 4 | markdown2linkpatternsfile: link-patterns.txt 5 | apisections: 6 | --- 7 | 12 | 13 | 16 | 17 | # sdcadm 18 | 19 | `sdcadm` is a tool that lives in the Triton headnode's GZ, for 20 | handling post-setup (i.e. setup steps after initial headnode setup), 21 | upgrades, listing of services and instances, health checks, and other setup 22 | duties. 23 | 24 | # Current status 25 | 26 | While `sdcadm` is still under significant development, and is far from complete, 27 | it is currently the recommended way to update SDC. Signs of incompleteness are 28 | that sub-commands of `sdcadm experimental ...` are required as part of the upgrade 29 | process. 30 | 31 | # Triton post-setup with sdcadm 32 | 33 | The document [post-setup](./post-setup.md) details the required steps in order to 34 | configure Triton DataCenter for practical usage, like HA setup and the 35 | addition of services not installed by default. 36 | 37 | # Manage Triton upgrades with sdcadm 38 | 39 | The document [update](./update.md) provides a detailed description on how to 40 | proceed with the update of a given Triton DataCenter (just "Triton" for 41 | short) standup. 42 | 43 | # Man page 44 | 45 | The [sdcadm man page](../man/man1/sdcadm.1.ronn) provides reference for every 46 | sdcadm subcommand. 47 | 48 | # Operator guide 49 | 50 | In depth Triton DataCenter [operator guide documentation](https://docs.joyent.com/private-cloud), including usage 51 | of sdcadm for many tasks, is also available. 52 | -------------------------------------------------------------------------------- /docs/link-patterns.txt: -------------------------------------------------------------------------------- 1 | /([A-Z]+-\d+)/ https://devhub.joyent.com/jira/browse/\1 2 | -------------------------------------------------------------------------------- /docs/post-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Triton post-setup with sdcadm 3 | markdown2extras: tables, code-friendly, cuddled-lists, link-patterns 4 | markdown2linkpatternsfile: link-patterns.txt 5 | apisections: 6 | --- 7 | 12 | 13 | 17 | 18 | # Triton post-setup with sdcadm 19 | 20 | The default setup of a Triton DataCenter is somewhat minimal. There are several 21 | post-setup steps required in order to get it configured for practical usage. 22 | 23 | ## Add external nics to imgapi and adminui 24 | 25 | These are required in order to be able to access remote update sources, and in 26 | order to be able to access AdminUI using a browser: 27 | 28 | sdcadm post-setup common-external-nics 29 | 30 | Please note that this command didn't wait for the "add nics" jobs to be 31 | completed, just submitted, so you might need to give it some extra time after 32 | the command exits until these jobs really finish. 33 | 34 | ## Create CloudAPI VM 35 | 36 | If non-administrator access to the Triton setup is planned, the CloudAPI zone 37 | must be created: 38 | 39 | sdcadm post-setup cloudapi 40 | 41 | 42 | ## Add Binder/Zookeeper service cluster to be used by Triton services 43 | 44 | By default, a Triton setup runs with a single zookeeper service running in the 45 | `binder` instance. This is not the recommended setup for a production 46 | environment; instead, it's recommended to create a *"cluster"* of 3 or 5 47 | binder service instances. 48 | 49 | In case this is a setup already being used by non-administrator users, it's a 50 | good idea to put the DC in maintenance first 51 | (`sdcadm dc-maint start`). Then: 52 | 53 | sdcadm post-setup ha-binder headnode CN1_UUID CN2_UUID 54 | 55 | This command will create 2 more binder instances, one placed on the CN 56 | identified by CN1\_UUID, and the other CN identified by CN2\_UUID. 57 | 58 | If you need to create a cluster of 5 instances, you just need to pass a couple 59 | additional CN UUIDs to this command. 60 | 61 | Once the binder instances have been configured, and all of them have joined 62 | the *"cluster"*, manatee and moray will be restarted to begin using this 63 | setup immediately. 64 | 65 | If you put the DC into maintenance, remember to recover it from such state 66 | by using `sdcadm dc-maint stop`, unless you want to proceed 67 | with ha-manatee too. 68 | 69 | 70 | ## Create the required manatee instances for HA 71 | 72 | When you have one manatee initially, you're in ONE\_NODE\_WRITE\_MODE, 73 | which is a special mode that exists just for bootstrapping. To go 74 | from this mode to a HA setup you'll need at least one more manatee. 75 | However, switching modes is not quite as simple as just provisioning a 76 | second manatee. It involves the following steps: 77 | 78 | - create a second manatee instance for you (with manatee-sitter disabled) 79 | - disable the ONE\_NODE\_WRITE\_MODE on the first instance 80 | - reboot the first manatee into multi-node mode 81 | - re-enable the sitter and reboot the second instance 82 | - wait for manatee to return that it's synchronized 83 | 84 | After we've gone through this, it'll create a 3rd manatee instance on the 85 | second server you specified to complete manatee HA setup. 86 | 87 | Aside all these details, all you need to run is: 88 | 89 | sdcadm post-setup ha-manatee \ 90 | --servers=`CN1_UUID` \ 91 | --servers=`CN2_UUID` 92 | 93 | It's always a good idea to run `sdcadm check-health` and `sdc-healthcheck` 94 | once this command has been completed, in order to review that everything 95 | reconnected to manatee/moray successfully. 96 | 97 | ## Create the desired number of moray instances for HA 98 | 99 | Finally, it's desirable to have more than the default single moray instance 100 | for HA. Creation of additional moray instances don't require any special 101 | command, just the standard `sdcadm create` used to create any additional 102 | instance of any service (see docs/index.md for the details). 103 | 104 | A recommended setup includes two additional moray instances created on the same 105 | CNs we added the manatees on the previous step: 106 | 107 | sdcadm create moray --server=CN1_UUID 108 | sdcadm create moray --server=CN2_UUID 109 | 110 | And that's it. With this, we should have a setup with multiple binder, 111 | manatee and moray instances, ready to operate with HA. As an additional step, 112 | if you plan to give access to non-administrator customers to your Triton setup 113 | (i.e. if you've installed CloudAPI), it would be handy to also have several 114 | mahi instances for HA. You can create them, and in general any additional 115 | instances for services "HA Ready", using the same procedure as for moray: 116 | 117 | sdcadm create mahi --server=CN1_UUID 118 | sdcadm create mahi --server=CN2_UUID 119 | 120 | ## Setup fabrics 121 | 122 | You can setup "fabrics" (Triton's network virtualization system) using the 123 | command: 124 | 125 | sdcadm post-setup fabrics -c /path/to/config.file 126 | 127 | where `conf` is a required configuration file. In order to understand the 128 | format of this configuration file there is detailed information about 129 | [fabrics setup in CoaL](https://github.com/TritonDataCenter/triton/blob/master/docs/developer-guide/coal-post-setup-fabrics.md) and general purpose information on fabrics from the 130 | [Triton networking and fabric operations guide](https://docs.tritondatacenter.com/private-cloud/networks/sdn). 131 | 132 | ### Create portolan HA instances 133 | 134 | Once `fabrics` setup has finished and the first `portolan0` instance 135 | has been created into the Headnode, additional HA instances can be 136 | created using `sdcadm create` subcommand: 137 | 138 | sdcadm create portolan --server=CN1_UUID 139 | sdcadm create portolan --server=CN2_UUID 140 | 141 | 142 | -------------------------------------------------------------------------------- /etc/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "updatesServerUrl": "https://updates.tritondatacenter.com", 3 | "vmMinPlatform": "20130606T000000Z", 4 | "imgNamesFromSvcName": { 5 | "assets": ["assets"], 6 | "imgapi": ["imgapi"], 7 | "dhcpd": ["dhcpd"], 8 | "docker": ["docker"], 9 | "cnapi": ["cnapi"], 10 | "cmon": ["cmon"], 11 | "cns": ["cns"], 12 | "vmapi": ["vmapi"], 13 | "manatee": ["sdc-postgres"], 14 | "rabbitmq": ["rabbitmq"], 15 | "workflow": ["workflow"], 16 | "papi": ["papi"], 17 | "fwapi": ["fwapi"], 18 | "adminui": ["adminui"], 19 | "dapi": ["dapi"], 20 | "redis": ["redis"], 21 | "binder": ["mantav2-nameservice", "manta-nameservice"], 22 | "mahi": ["mantav2-authcache", "manta-authcache"], 23 | "sdc": ["sdc"], 24 | "sapi": ["sapi"], 25 | "cloudapi": ["cloudapi"], 26 | "napi": ["napi"], 27 | "portolan": ["portolan"], 28 | "moray": ["mantav2-moray", "manta-moray"], 29 | "amonredis": ["amonredis"], 30 | "ufds": ["ufds"], 31 | "amon": ["amon"], 32 | "cabase": ["cabase"], 33 | "hagfish-watcher": ["hagfish-watcher"], 34 | "agents_core": ["agents_core"], 35 | "firewaller": ["firewaller"], 36 | "amon-agent": ["amon-agent"], 37 | "cainstsvc": ["cainstsvc"], 38 | "amon-relay": ["amon-relay"], 39 | "smartlogin": ["smartlogin"], 40 | "cmon-agent": ["cmon-agent"], 41 | "cn-agent": ["cn-agent"], 42 | "vm-agent": ["vm-agent"], 43 | "net-agent": ["net-agent"], 44 | "config-agent": ["config-agent"], 45 | "dockerlogger": ["dockerlogger"], 46 | "volapi": ["volapi"], 47 | "logarchiver": ["logarchiver"], 48 | "kbmapi": ["kbmapi"], 49 | "prometheus": ["mantav2-prometheus", "manta-prometheus"], 50 | "grafana": ["grafana"], 51 | "firewall-logger-agent": ["firewall-logger-agent"] 52 | }, 53 | "svcMinImages": { 54 | "binder": "20140731T211135Z", 55 | "mahi": "20140618T020241Z", 56 | "imgapi": "20140619T081912Z", 57 | "amon": "20140618T014459Z", 58 | "amonredis": "20140528T053251Z", 59 | "dhcpd": "20140618T034923Z", 60 | "fwapi": "20140618T014938Z", 61 | "papi": "20140618T113529Z", 62 | "redis": "20140618T035133Z", 63 | "sapi": "20140618T034916Z", 64 | "workflow": "20140702T155100Z", 65 | "cnapi": "20140710T224122Z", 66 | "napi": "20140710T172945Z", 67 | "vmapi": "20140710T171616Z", 68 | "moray": "20140710T181209Z", 69 | "adminui": "20140718T210958Z", 70 | "manatee": "20141225T181254Z", 71 | "sdc": "20140725T191132Z", 72 | "cloudapi": "20140731T175858Z", 73 | "ufds": "20140728T173339Z" 74 | }, 75 | "updatedSizeParameters": { 76 | "dhcpd": { 77 | "max_physical_memory": 512, 78 | "max_locked_memory": 512, 79 | "max_swap": 1024, 80 | "cpu_cap": 200, 81 | "package_name": "sdc_512" 82 | }, 83 | "manatee": { 84 | "max_lwps": 4000 85 | }, 86 | "binder": { 87 | "max_physical_memory": 1024, 88 | "max_locked_memory": 1024, 89 | "max_swap": 2048 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /etc/sample-packages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "sample-128M", 4 | "version": "1.0.0", 5 | "active": true, 6 | "max_lwps": 4000, 7 | "max_physical_memory": 128, 8 | "max_swap": 512, 9 | "quota": 3072, 10 | "zfs_io_priority": 8, 11 | "fss": 8, 12 | "cpu_cap": 25, 13 | "billing_tag": "sample-128M", 14 | "default": false, 15 | "v": 1, 16 | "vcpus": 1 17 | 18 | }, 19 | { 20 | "name": "sample-256M", 21 | "version": "1.0.0", 22 | "active": true, 23 | "max_lwps": 4000, 24 | "max_physical_memory": 256, 25 | "max_swap": 1024, 26 | "quota": 6144, 27 | "zfs_io_priority": 16, 28 | "fss": 16, 29 | "cpu_cap": 25, 30 | "billing_tag": "sample-256M", 31 | "default": false, 32 | "v": 1, 33 | "vcpus": 1 34 | 35 | }, 36 | { 37 | "name": "sample-512M", 38 | "version": "1.0.0", 39 | "active": true, 40 | "max_lwps": 4000, 41 | "max_physical_memory": 512, 42 | "max_swap": 2048, 43 | "quota": 12288, 44 | "zfs_io_priority": 32, 45 | "fss": 32, 46 | "cpu_cap": 50, 47 | "billing_tag": "sample-512M", 48 | "default": false, 49 | "v": 1, 50 | "vcpus": 1 51 | 52 | }, 53 | { 54 | "name": "sample-1G", 55 | "version": "1.0.0", 56 | "active": true, 57 | "max_lwps": 4000, 58 | "max_physical_memory": 1024, 59 | "max_swap": 4096, 60 | "quota": 25600, 61 | "zfs_io_priority": 64, 62 | "fss": 64, 63 | "cpu_cap": 100, 64 | "billing_tag": "sample-1G", 65 | "default": true, 66 | "v": 1, 67 | "vcpus": 1 68 | }, 69 | { 70 | "name": "sample-2G", 71 | "version": "1.0.0", 72 | "active": true, 73 | "max_lwps": 4000, 74 | "max_physical_memory": 2048, 75 | "max_swap": 8192, 76 | "quota": 51200, 77 | "zfs_io_priority": 128, 78 | "fss": 128, 79 | "cpu_cap": 200, 80 | "billing_tag": "sample-2G", 81 | "default": false, 82 | "v": 1, 83 | "vcpus": 1 84 | }, 85 | { 86 | "name": "sample-4G", 87 | "version": "1.0.0", 88 | "active": true, 89 | "max_lwps": 4000, 90 | "max_physical_memory": 4096, 91 | "max_swap": 16384, 92 | "quota": 102400, 93 | "zfs_io_priority": 256, 94 | "fss": 256, 95 | "cpu_cap": 400, 96 | "billing_tag": "sample-4G", 97 | "default": false, 98 | "v": 1, 99 | "vcpus": 1 100 | }, 101 | { 102 | "name": "sample-8G", 103 | "version": "1.0.0", 104 | "active": true, 105 | "max_lwps": 4000, 106 | "max_physical_memory": 8192, 107 | "max_swap": 32768, 108 | "quota": 204800, 109 | "zfs_io_priority": 512, 110 | "fss": 512, 111 | "cpu_cap": 800, 112 | "billing_tag": "sample-8G", 113 | "default": false, 114 | "v": 1, 115 | "vcpus": 1 116 | }, 117 | { 118 | "name": "sample-16G", 119 | "version": "1.0.0", 120 | "active": true, 121 | "max_lwps": 4000, 122 | "max_physical_memory": 16384, 123 | "max_swap": 65536, 124 | "quota": 409600, 125 | "zfs_io_priority": 1024, 126 | "fss": 1024, 127 | "cpu_cap": 1600, 128 | "billing_tag": "sample-16G", 129 | "default": false, 130 | "v": 1, 131 | "vcpus": 1 132 | }, 133 | { 134 | "name": "sample-32G", 135 | "version": "1.0.0", 136 | "active": true, 137 | "max_lwps": 4000, 138 | "max_physical_memory": 32768, 139 | "max_swap": 131072, 140 | "quota": 819200, 141 | "zfs_io_priority": 2048, 142 | "fss": 2048, 143 | "cpu_cap": 3200, 144 | "billing_tag": "sample-32G", 145 | "default": false, 146 | "v": 1, 147 | "vcpus": 1 148 | }, 149 | { 150 | "name": "sample-64G", 151 | "version": "1.0.0", 152 | "active": true, 153 | "max_lwps": 4000, 154 | "max_physical_memory": 65536, 155 | "max_swap": 262144, 156 | "quota": 1638400, 157 | "zfs_io_priority": 4096, 158 | "fss": 4096, 159 | "cpu_cap": 6400, 160 | "billing_tag": "sample-64G", 161 | "default": false, 162 | "v": 1, 163 | "vcpus": 1 164 | }, 165 | { 166 | "name": "sample-bhyve-flexible-1G", 167 | "version": "1.0.0", 168 | "active": true, 169 | "max_lwps": 4000, 170 | "max_physical_memory": 1024, 171 | "max_swap": 4096, 172 | "quota": 24576, 173 | "zfs_io_priority": 64, 174 | "fss": 64, 175 | "cpu_cap": 100, 176 | "billing_tag": "sample-bhyve-flexible-1G", 177 | "default": true, 178 | "v": 1, 179 | "vcpus": 1, 180 | "brand": "bhyve", 181 | "flexible_disk": true 182 | }, 183 | { 184 | "name": "sample-bhyve-reserved-snapshots-space", 185 | "version": "1.0.0", 186 | "active": true, 187 | "max_lwps": 4000, 188 | "max_physical_memory": 1024, 189 | "max_swap": 4096, 190 | "quota": 40960, 191 | "zfs_io_priority": 64, 192 | "fss": 64, 193 | "cpu_cap": 100, 194 | "billing_tag": "sample-bhyve-reserved-snapshots-space", 195 | "default": true, 196 | "v": 1, 197 | "vcpus": 1, 198 | "brand": "bhyve", 199 | "flexible_disk": true, 200 | "disks": [ 201 | { }, 202 | { "size": 10240 } 203 | ] 204 | }, 205 | { 206 | "name": "sample-bhyve-three-disks", 207 | "version": "1.0.0", 208 | "active": true, 209 | "max_lwps": 4000, 210 | "max_physical_memory": 1024, 211 | "max_swap": 4096, 212 | "quota": 24576, 213 | "zfs_io_priority": 64, 214 | "fss": 64, 215 | "cpu_cap": 100, 216 | "billing_tag": "sample-bhyve-three-disks", 217 | "default": true, 218 | "v": 1, 219 | "vcpus": 1, 220 | "brand": "bhyve", 221 | "flexible_disk": true, 222 | "disks": [ 223 | { }, 224 | { "size": 6144 }, 225 | { "size": "remaining"} 226 | ] 227 | } 228 | ] 229 | -------------------------------------------------------------------------------- /etc/setup/user-script: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | export PS4='[\D{%FT%TZ}] ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 13 | 14 | set -o xtrace 15 | set -o errexit 16 | set -o pipefail 17 | 18 | # 19 | # The presence of the /var/svc/.ran-user-script file indicates that the 20 | # instance has already been setup (i.e. the instance has booted previously). 21 | # 22 | # Upon first boot, run the setup.sh script if present. On all boots including 23 | # the first one, run the configure.sh script if present. 24 | # 25 | 26 | SENTINEL=/var/svc/.ran-user-script 27 | 28 | DIR=/opt/smartdc/boot 29 | 30 | if [[ ! -e ${SENTINEL} ]]; then 31 | if [[ -f ${DIR}/setup.sh ]]; then 32 | ${DIR}/setup.sh 2>&1 | tee /var/svc/setup.log 33 | fi 34 | 35 | touch ${SENTINEL} 36 | fi 37 | 38 | if [[ ! -f ${DIR}/configure.sh ]]; then 39 | echo "Missing ${DIR}/configure.sh cannot configure." 40 | exit 1 41 | fi 42 | 43 | exec ${DIR}/configure.sh 44 | -------------------------------------------------------------------------------- /lib/cli/do_add_new_agent_svcs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018 Joyent, Inc. 9 | */ 10 | 11 | var steps = require('../steps'); 12 | 13 | /* 14 | * The 'sdcadm experimental add-new-agent-svcs' CLI subcommand. 15 | */ 16 | function do_add_new_agent_svcs(_subcmd, _opts, _args, cb) { 17 | console.error( 18 | 'Warning: "sdcadm experimental add-new-agent-svcs" is deprecated.'); 19 | console.error('Use "sdcadm experimental update-other" or'); 20 | console.error('"sdcadm experimental update-agents".\n'); 21 | steps.sapi.ensureAgentServices({ 22 | progress: this.progress, 23 | sdcadm: this.sdcadm, 24 | log: this.log 25 | }, cb); 26 | } 27 | 28 | do_add_new_agent_svcs.options = [ { 29 | names: ['help', 'h'], 30 | type: 'bool', 31 | help: 'Show this help.' 32 | }]; 33 | 34 | do_add_new_agent_svcs.help = [ 35 | 'DEPRECATED. Ensure a SAPI service exists for each core Triton agent.', 36 | '', 37 | 'This is deprecated, both "sdcadm experimental update-agents" and', 38 | '"sdcadm experimental update-other" now provide this functionality.', 39 | '', 40 | 'Usage:', 41 | ' {{name}} add-new-agent-svcs\n', 42 | '', 43 | '{{options}}' 44 | ].join('\n'); 45 | 46 | do_add_new_agent_svcs.hidden = true; 47 | 48 | do_add_new_agent_svcs.logToFile = true; 49 | 50 | // --- exports 51 | 52 | module.exports = { 53 | do_add_new_agent_svcs: do_add_new_agent_svcs 54 | }; 55 | -------------------------------------------------------------------------------- /lib/cli/do_completion.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018 Joyent, Inc. 9 | * 10 | * `sdcadm completion` 11 | */ 12 | 13 | function do_completion(subcmd, opts, _args, cb) { 14 | if (opts.help) { 15 | this.do_help('help', {}, [subcmd], cb); 16 | return; 17 | } 18 | 19 | console.log(this.bashCompletion({includeHidden: true})); 20 | cb(); 21 | } 22 | 23 | do_completion.options = [ 24 | { 25 | names: ['help', 'h'], 26 | type: 'bool', 27 | help: 'Show this help.' 28 | } 29 | ]; 30 | do_completion.help = [ 31 | 'Output bash completion code for the `sdcadm` CLI.', 32 | '', 33 | 'By default, sdcadm installation should setup for Bash completion.', 34 | 'However, you can update the completions as follows:', 35 | '', 36 | ' sdcadm completion >/opt/smartdc/sdcadm/etc/sdcadm.completion \\', 37 | ' && source /opt/smartdc/sdcadm/etc/sdcadm.completion' 38 | ].join('\n'); 39 | do_completion.hidden = true; 40 | 41 | module.exports = do_completion; 42 | -------------------------------------------------------------------------------- /lib/cli/do_fix_core_vm_resolvers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | var util = require('util'); 12 | var format = util.format; 13 | 14 | var common = require('../common'); 15 | var errors = require('../errors'); 16 | var steps = require('../steps'); 17 | 18 | function do_fix_core_vm_resolvers(subcmd, opts, args, cb) { 19 | var self = this; 20 | var progress = self.progress; 21 | var log = self.log; 22 | 23 | if (opts.help) { 24 | this.do_help('help', {}, [subcmd], cb); 25 | return; 26 | } else if (args.length > 0) { 27 | cb(new errors.UsageError('too many args: ' + args)); 28 | return; 29 | } 30 | 31 | self.sdcadm.ensureSdcApp({}, function (appErr) { 32 | if (appErr) { 33 | cb(appErr); 34 | return; 35 | } 36 | steps.binder.checkCoreVmInstancesResolvers({ 37 | sdcadm: self.sdcadm, 38 | progress: progress, 39 | log: log, 40 | adminOnly: opts.admin_only 41 | }, function (err, resolvers) { 42 | if (err) { 43 | cb(err); 44 | return; 45 | } 46 | 47 | Object.keys(resolvers).forEach(function (r) { 48 | progress( 49 | format('VM %s (%s) resolvers need to be updated\n', r, 50 | resolvers[r].alias) + 51 | common.indent(format('from [%s] to [%s]', 52 | resolvers[r].current.join(', '), 53 | resolvers[r].expected.join(', ')))); 54 | }); 55 | 56 | if (opts.dry_run) { 57 | cb(); 58 | return; 59 | } 60 | 61 | steps.binder.updateCoreVmsResolvers({ 62 | sdcadm: self.sdcadm, 63 | progress: progress, 64 | log: log, 65 | fixableResolvers: resolvers 66 | }, cb); 67 | }); 68 | }); 69 | } 70 | 71 | 72 | do_fix_core_vm_resolvers.options = [ 73 | { 74 | names: ['help', 'h'], 75 | type: 'bool', 76 | help: 'Show this help.' 77 | }, 78 | { 79 | names: ['dry-run', 'n'], 80 | type: 'bool', 81 | help: 'Go through the motions without actually updating.' 82 | } 83 | ]; 84 | do_fix_core_vm_resolvers.help = ( 85 | 'Update resolvers for core VMs on the admin network.\n' + 86 | 'This will be integrated into "sdcadm post-setup ha-binder".\n' + 87 | '\n' + 88 | 'Usage:\n' + 89 | ' {{name}} fix-core-vm-resolvers\n' + 90 | '\n' + 91 | '{{options}}' + 92 | '\n' + 93 | 'Note: Currently this skips core VMs such as "nat" zones ' + 94 | 'that are not on the admin network.\n' 95 | ); 96 | 97 | do_fix_core_vm_resolvers.logToFile = true; 98 | 99 | // --- exports 100 | 101 | module.exports = { 102 | do_fix_core_vm_resolvers: do_fix_core_vm_resolvers 103 | }; 104 | 105 | // vim: set softtabstop=4 shiftwidth=4: 106 | -------------------------------------------------------------------------------- /lib/cli/do_info.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | var errors = require('../errors'); 12 | 13 | /* 14 | * The 'sdcadm services (svcs)' CLI subcommand. 15 | */ 16 | 17 | function do_info(subcmd, opts, args, callback) { 18 | var self = this; 19 | 20 | if (opts.help) { 21 | this.do_help('help', {}, [subcmd], callback); 22 | return; 23 | } else if (args.length !== 0) { 24 | callback(new errors.UsageError('too many args: ' + args)); 25 | return; 26 | } 27 | 28 | var cfg = self.sdcadm.config; 29 | 30 | if (opts.json) { 31 | console.log(JSON.stringify(cfg, null, 2)); 32 | callback(); 33 | return; 34 | } 35 | 36 | console.log('Datacenter Company Name: %s', 37 | cfg.datacenter_company_name); 38 | console.log('Datacenter Name: %s', 39 | cfg.datacenter_name); 40 | console.log('Datacenter Location: %s', 41 | cfg.datacenter_location); 42 | 43 | console.log(); 44 | 45 | console.log('Admin IP: %s', 46 | cfg.admin_ip); 47 | console.log('External IP: %s', 48 | cfg.external_ip); 49 | console.log('DNS Domain: %s', 50 | cfg.dns_domain); 51 | 52 | callback(); 53 | } 54 | do_info.options = [ 55 | { 56 | names: ['help', 'h'], 57 | type: 'bool', 58 | help: 'Show this help.' 59 | }, 60 | { 61 | names: ['json', 'j'], 62 | type: 'bool', 63 | help: 'JSON output' 64 | } 65 | ]; 66 | do_info.aliases = ['config']; 67 | do_info.help = ( 68 | 'Get SDC info.\n' 69 | + '\n' 70 | + 'Usage:\n' 71 | + ' {{name}} info []\n' 72 | + '\n' 73 | + '{{options}}' 74 | ); 75 | 76 | // --- exports 77 | 78 | module.exports = { 79 | do_info: do_info 80 | }; 81 | -------------------------------------------------------------------------------- /lib/cli/do_install_docker_cert.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | var vasync = require('vasync'); 12 | 13 | var common = require('../common'); 14 | var errors = require('../errors'); 15 | var svcadm = require('../svcadm'); 16 | 17 | /* 18 | * The 'sdcadm experimental install-docker-cert' CLI subcommand. 19 | */ 20 | 21 | /** 22 | * Installs a custom TLS certificate for the sdc-docker service. By default 23 | * sdc-docker uses a self-signed certificate that gets created when the zone 24 | * is created for the first time. This command allows installing a custom 25 | * certificate to be used by sdc-docker. 26 | */ 27 | 28 | function do_install_docker_cert(_subcmd, opts, _args, cb) { 29 | var self = this; 30 | var dockerVm; 31 | 32 | if (!opts.key) { 33 | cb(new errors.UsageError( 34 | 'must specify certificate key path (-k or --key)')); 35 | return; 36 | } 37 | if (!opts.cert) { 38 | cb(new errors.UsageError( 39 | 'must specify certificate path (-c or --cert)')); 40 | return; 41 | } 42 | 43 | vasync.pipeline({funcs: [ 44 | function ensureDockerInstance(_, next) { 45 | var filters = { 46 | state: 'active', 47 | owner_uuid: self.sdcadm.config.ufds_admin_uuid, 48 | 'tag.smartdc_role': 'docker' 49 | }; 50 | self.sdcadm.vmapi.listVms(filters, function (vmsErr, vms) { 51 | if (vmsErr) { 52 | next(vmsErr); 53 | return; 54 | } 55 | if (Array.isArray(vms) && !vms.length) { 56 | next(new errors.UpdateError('no "docker" VM ' + 57 | 'instance found')); 58 | return; 59 | } 60 | dockerVm = vms[0]; 61 | next(); 62 | }); 63 | }, 64 | 65 | function installKey(_, next) { 66 | self.progress('Installing certificate'); 67 | var argv = [ 68 | 'cp', 69 | opts.key, 70 | '/zones/' + dockerVm.uuid + '/root/data/tls/key.pem' 71 | ]; 72 | 73 | common.execFilePlus({ 74 | argv: argv, 75 | log: self.log 76 | }, function (err, stdout, stderr) { 77 | self.log.trace({cmd: argv.join(' '), err: err, stdout: stdout, 78 | stderr: stderr}, 'ran cp command'); 79 | if (err) { 80 | next(new errors.InternalError({ 81 | message: 'error installing certificate key', 82 | cmd: argv.join(' '), 83 | stdout: stdout, 84 | stderr: stderr, 85 | cause: err 86 | })); 87 | return; 88 | } 89 | next(); 90 | }); 91 | }, 92 | 93 | function installCertificate(_, next) { 94 | var argv = [ 95 | 'cp', 96 | opts.cert, 97 | '/zones/' + dockerVm.uuid + '/root/data/tls/cert.pem' 98 | ]; 99 | 100 | common.execFilePlus({ 101 | argv: argv, 102 | log: self.log 103 | }, function (err, stdout, stderr) { 104 | self.log.trace({cmd: argv.join(' '), err: err, stdout: stdout, 105 | stderr: stderr}, 'ran cp command'); 106 | if (err) { 107 | next(new errors.InternalError({ 108 | message: 'error installing certificate', 109 | cmd: argv.join(' '), 110 | stdout: stdout, 111 | stderr: stderr, 112 | cause: err 113 | })); 114 | return; 115 | } 116 | next(); 117 | }); 118 | }, 119 | 120 | function restartSdcDockerSvc(_, next) { 121 | self.progress('Restarting sdc-docker service'); 122 | 123 | svcadm.svcadmRestart({ 124 | fmri: 'docker', 125 | zone: dockerVm.uuid, 126 | log: self.log 127 | }, next); 128 | } 129 | ]}, cb); 130 | } 131 | 132 | do_install_docker_cert.options = [ 133 | { 134 | names: ['help', 'h'], 135 | type: 'bool', 136 | help: 'Show this help.' 137 | }, 138 | { 139 | names: ['cert', 'c'], 140 | type: 'string', 141 | help: 'Path to certificate.' 142 | }, 143 | { 144 | names: ['key', 'k'], 145 | type: 'string', 146 | help: 'Path to private key.' 147 | } 148 | ]; 149 | do_install_docker_cert.help = ( 150 | 'Installs a custom TLS certificate to be used by sdc-docker.\n' + 151 | '\n' + 152 | 'Usage:\n' + 153 | ' {{name}} install-docker-cert\n' + 154 | '\n' + 155 | '{{options}}' 156 | ); 157 | 158 | do_install_docker_cert.logToFile = true; 159 | 160 | // --- exports 161 | 162 | module.exports = { 163 | do_install_docker_cert: do_install_docker_cert 164 | }; 165 | -------------------------------------------------------------------------------- /lib/cli/do_instances.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | var tabula = require('tabula'); 12 | 13 | var common = require('../common'); 14 | var errors = require('../errors'); 15 | 16 | /* 17 | * The 'sdcadm instances (insts)' CLI subcommand. 18 | */ 19 | 20 | function do_instances(subcmd, opts, args, callback) { 21 | var self = this; 22 | if (opts.help) { 23 | this.do_help('help', {}, [subcmd], callback); 24 | return; 25 | } 26 | 27 | var validTypes = ['vm', 'agent']; 28 | var listOpts = {}; 29 | for (var i = 0; i < args.length; i++) { 30 | var arg = args[i]; 31 | var k = 'svc'; 32 | var v = arg; 33 | var equal = arg.indexOf('='); 34 | if (equal !== -1) { 35 | k = arg.slice(0, equal); 36 | v = arg.slice(equal + 1); 37 | } 38 | switch (k) { 39 | case 'svc': 40 | if (!listOpts.svcs) { 41 | listOpts.svcs = []; 42 | } 43 | listOpts.svcs.push(v); 44 | break; 45 | case 'type': 46 | if (validTypes.indexOf(v) === -1) { 47 | callback(new errors.UsageError( 48 | 'invalid instance type: ' + v)); 49 | return; 50 | } 51 | if (!listOpts.types) { 52 | listOpts.types = []; 53 | } 54 | listOpts.types.push(v); 55 | break; 56 | default: 57 | callback(new errors.UsageError( 58 | 'unknown filter "' + k + '"')); 59 | return; 60 | } 61 | } 62 | 63 | var columns = opts.o.trim().split(/\s*,\s*/g); 64 | var sort = opts.s.trim().split(/\s*,\s*/g); 65 | 66 | self.sdcadm.listInsts(listOpts, function (err, insts) { 67 | if (err) { 68 | callback(err); 69 | return; 70 | } 71 | 72 | var rows = insts; 73 | if (opts.group_by_image) { 74 | var rowFromTypeSvcImage = {}; 75 | for (var j = 0; j < insts.length; j++) { 76 | var inst = insts[j]; 77 | // `|| inst.version` necessary until agents and platforms 78 | // use images. 79 | var key = [inst.type, inst.service, 80 | inst.image || inst.version].join('/'); 81 | if (rowFromTypeSvcImage[key] === undefined) { 82 | rowFromTypeSvcImage[key] = { 83 | type: inst.type, 84 | service: inst.service, 85 | version: inst.version, 86 | image: inst.image, 87 | instances: [inst.instance] 88 | }; 89 | } else { 90 | rowFromTypeSvcImage[key].instances.push(inst.instance); 91 | } 92 | } 93 | rows = Object.keys(rowFromTypeSvcImage).map(function (tsi) { 94 | var row = rowFromTypeSvcImage[tsi]; 95 | row.count = row.instances.length; 96 | return row; 97 | }); 98 | columns = ['service', 'image', 'version', 'count']; 99 | } 100 | 101 | common.sortArrayOfObjects(rows, sort); 102 | if (opts.json) { 103 | console.log(JSON.stringify(rows, null, 4)); 104 | } else { 105 | tabula(rows, { 106 | skipHeader: opts.H, 107 | columns: columns 108 | }); 109 | } 110 | callback(); 111 | }); 112 | } 113 | do_instances.options = [ 114 | { 115 | names: ['help', 'h'], 116 | type: 'bool', 117 | help: 'Show this help.' 118 | }, 119 | { 120 | names: ['json', 'j'], 121 | type: 'bool', 122 | help: 'JSON output' 123 | }, 124 | { 125 | names: ['H'], 126 | type: 'bool', 127 | help: 'Omit table header row.' 128 | }, 129 | { 130 | names: ['o'], 131 | type: 'string', 132 | default: 'instance,service,hostname,version,alias', 133 | help: 'Specify fields (columns) to output.', 134 | helpArg: 'field1,...' 135 | }, 136 | { 137 | names: ['s'], 138 | type: 'string', 139 | default: '-type,service,hostname,version,alias', 140 | help: 'Sort on the given fields. Default is ' + 141 | '"-type,service,hostname,version,alias".', 142 | helpArg: 'field1,...' 143 | }, 144 | { 145 | names: ['group-by-image', 'I'], 146 | type: 'bool', 147 | help: 'Group by unique (service, image).' 148 | } 149 | ]; 150 | do_instances.aliases = ['insts']; 151 | do_instances.help = ( 152 | 'List all (or a filtered subset of) SDC service instances.\n' + 153 | 'Note that "service" here includes SDC core vms and global zone agents.\n' + 154 | '\n' + 155 | 'Usage:\n' + 156 | ' {{name}} instances [] [...]\n' + 157 | '\n' + 158 | '{{options}}\n' + 159 | 'Instances can be filtered via by type:\n' + 160 | ' type=vm\n' + 161 | ' type=agent\n' + 162 | 'and service name:\n' + 163 | ' svc=imgapi\n' + 164 | ' imgapi\n' + 165 | ' cnapi cn-agent\n' 166 | ); 167 | 168 | // --- exports 169 | 170 | module.exports = { 171 | do_instances: do_instances 172 | }; 173 | -------------------------------------------------------------------------------- /lib/cli/do_remove_ca.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2019, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * The 'sdcadm experimental remove-ca' CLI subcommand to remove the Cloud 13 | * Analytics (CA) service from TritonDC. 14 | */ 15 | 16 | var RemoveServicesProcedure = require('../procedures/remove-services') 17 | .RemoveServicesProcedure; 18 | var runProcs = require('../procedures').runProcs; 19 | 20 | function do_remove_ca(subcmd, opts, _args, cb) { 21 | var self = this; 22 | 23 | if (opts.help) { 24 | this.do_help('help', {}, [subcmd], cb); 25 | return; 26 | } 27 | 28 | var procs = [ 29 | new RemoveServicesProcedure({ 30 | svcNames: ['ca', 'cabase', 'cainstsvc'], 31 | includeServerNames: opts.servers, 32 | excludeServerNames: opts.exclude_servers 33 | }) 34 | ]; 35 | runProcs({ 36 | log: self.log, 37 | procs: procs, 38 | sdcadm: self.sdcadm, 39 | ui: self.ui, 40 | dryRun: opts.dry_run, 41 | skipConfirm: opts.yes 42 | }, function done(err) { 43 | cb(err); 44 | }); 45 | } 46 | 47 | do_remove_ca.options = [ 48 | { 49 | names: ['help', 'h'], 50 | type: 'bool', 51 | help: 'Show this help.' 52 | }, 53 | { 54 | names: ['yes', 'y'], 55 | type: 'bool', 56 | help: 'Answer yes to all confirmations.' 57 | }, 58 | { 59 | names: ['dry-run', 'n'], 60 | type: 'bool', 61 | help: 'Do a dry-run.' 62 | }, 63 | { 64 | group: 'Server selection (by default agents on all setup servers ' + 65 | 'are removed)' 66 | }, 67 | { 68 | names: ['servers', 's'], 69 | type: 'arrayOfCommaSepString', 70 | helpArg: 'NAMES', 71 | help: 'Comma-separated list of servers (either hostnames or uuids) ' + 72 | 'where agents will be removed.' 73 | }, 74 | { 75 | names: ['exclude-servers', 'S'], 76 | type: 'arrayOfCommaSepString', 77 | helpArg: 'NAMES', 78 | help: 'Comma-separated list of servers (either hostnames or uuids) ' + 79 | 'to exclude from the set of servers on which agents will be ' + 80 | 'removed.' 81 | } 82 | ]; 83 | 84 | do_remove_ca.helpOpts = { 85 | maxHelpCol: 25 86 | }; 87 | 88 | do_remove_ca.help = [ 89 | 'Remove the Cloud Analytics services from Triton.', 90 | '', 91 | 'Usage:', 92 | ' {{name}} remove-ca', 93 | '', 94 | '{{options}}', 95 | 'Cloud Analytics (CA) has been deprecated. This command will remove CA', 96 | 'related service agents and VMs.' 97 | ].join('\n'); 98 | 99 | do_remove_ca.logToFile = true; 100 | 101 | // --- exports 102 | 103 | module.exports = { 104 | do_remove_ca: do_remove_ca 105 | }; 106 | -------------------------------------------------------------------------------- /lib/cli/do_rollback.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | var p = console.log; 12 | var fs = require('fs'); 13 | var path = require('path'); 14 | 15 | var vasync = require('vasync'); 16 | 17 | var common = require('../common'); 18 | var errors = require('../errors'); 19 | 20 | /* 21 | * The 'sdcadm rollback' CLI subcommand. 22 | */ 23 | 24 | function do_rollback(subcmd, opts, _args, cb) { 25 | var self = this; 26 | if (opts.help) { 27 | this.do_help('help', {}, [subcmd], cb); 28 | return; 29 | } 30 | 31 | // TODO: When no file is given, read latest from /var/sdcadm/updates 32 | // (or maybe just add '--latest' option, like for platform cmd) 33 | if (!opts.file) { 34 | cb(new errors.UsageError('File including update plan ' + 35 | 'to rollback must be specified')); 36 | return; 37 | } 38 | 39 | if (!opts.force) { 40 | cb(new errors.UsageError('Migrations and version ' + 41 | 'dependencies not implemented. Use "--force" to rollback ' + 42 | '(warning: you know what you are doing w.r.t. migrations).')); 43 | return; 44 | } 45 | 46 | var upPlan; 47 | var plan; 48 | var unlock; 49 | var execStart; 50 | 51 | vasync.pipeline({funcs: [ 52 | function getSpecFromFile(_, next) { 53 | fs.readFile(opts.file, { 54 | encoding: 'utf8' 55 | }, function rfCb(err, data) { 56 | if (err) { 57 | // TODO: InternalError 58 | next(err); 59 | return; 60 | } 61 | upPlan = JSON.parse(data); // presume no parse error 62 | next(); 63 | }); 64 | }, 65 | function getLock(_, next) { 66 | self.sdcadm.acquireLock({progress: self.progress}, 67 | function (lockErr, unlock_) { 68 | unlock = unlock_; 69 | next(lockErr); 70 | }); 71 | }, 72 | function genRbPlan(_, next) { 73 | self.sdcadm.genRollbackPlan({ 74 | updatePlan: upPlan 75 | }, function (err, _plan) { 76 | if (err) { 77 | next(err); 78 | return; 79 | } 80 | plan = _plan; 81 | next(); 82 | }); 83 | }, 84 | 85 | function confirm(_, next) { 86 | if (plan.procs.length === 0) { 87 | next(); 88 | return; 89 | } 90 | p(''); 91 | p('This rollback will make the following changes:'); 92 | self.sdcadm.summarizePlan({plan: plan, progress: self.progress}); 93 | p(''); 94 | if (opts.yes) { 95 | next(); 96 | return; 97 | } 98 | var msg = 'Would you like to continue? [y/N] '; 99 | common.promptYesNo({msg: msg, default: 'n'}, function (answer) { 100 | if (answer !== 'y') { 101 | p('Aborting rollback'); 102 | cb(); 103 | return; 104 | } 105 | p(''); 106 | next(); 107 | }); 108 | }, 109 | 110 | function execPlan(_, next) { 111 | execStart = Date.now(); 112 | if (plan.procs.length === 0) { 113 | next(); 114 | return; 115 | } 116 | if (opts.dry_run) { 117 | p('[dry-run] done'); 118 | next(); 119 | return; 120 | } 121 | self.sdcadm.execUpdatePlan({ 122 | plan: plan, 123 | progress: self.progress, 124 | ui: self.ui, 125 | dryRun: opts.dry_run, 126 | uuid: self.uuid, 127 | upDir: path.dirname(opts.file) 128 | }, next); 129 | } 130 | ] 131 | }, function finishRb(err) { 132 | vasync.pipeline({funcs: [ 133 | function dropLock(_, next) { 134 | if (!unlock) { 135 | next(); 136 | return; 137 | } 138 | self.sdcadm.releaseLock({unlock: unlock}, next); 139 | } 140 | ]}, function done(finishRbErr) { 141 | // We shouldn't ever get a `finishRbErr`. Let's be loud if we do. 142 | if (finishRbErr) { 143 | self.log.fatal({err: finishRbErr}, 144 | 'unexpected error finishing up rollback'); 145 | } 146 | if (err || finishRbErr) { 147 | cb(err || finishRbErr); 148 | return; 149 | } 150 | 151 | if (plan.procs.length === 0) { 152 | p('Nothing to rollback'); 153 | } else { 154 | p('Rolledback successfully (elapsed %ds).', 155 | Math.floor((Date.now() - execStart) / 1000)); 156 | } 157 | cb(); 158 | }); 159 | }); 160 | } 161 | 162 | 163 | do_rollback.options = [ 164 | { 165 | names: ['help', 'h'], 166 | type: 'bool', 167 | help: 'Show this help.' 168 | }, 169 | { 170 | names: ['dry-run', 'n'], 171 | type: 'bool', 172 | help: 'Go through the motions without actually rolling back.' 173 | }, 174 | { 175 | names: ['yes', 'y'], 176 | type: 'bool', 177 | help: 'Answer yes to all confirmations.' 178 | }, 179 | { 180 | names: ['force'], 181 | type: 'bool', 182 | help: 'Do the rollback despite of migrations and version dependencies' 183 | }, 184 | { 185 | names: ['file', 'f'], 186 | type: 'string', 187 | help: 'Full path to file with update plan.json to rollback', 188 | helpArg: 'FILE_PATH' 189 | } 190 | ]; 191 | 192 | 193 | do_rollback.help = ( 194 | 'Rollback SDC services and instances.\n' + 195 | '\n' + 196 | 'Usage:\n' + 197 | ' {{name}} rollback [] -f <./local-upgrade-file.json> ...\n' + 198 | '\n' + 199 | '{{options}}' 200 | ); 201 | 202 | do_rollback.logToFile = true; 203 | 204 | // --- exports 205 | 206 | module.exports = { 207 | do_rollback: do_rollback 208 | }; 209 | -------------------------------------------------------------------------------- /lib/cli/do_self_update.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | var vasync = require('vasync'); 12 | 13 | var errors = require('../errors'); 14 | 15 | /* 16 | * The 'sdcadm self-update' CLI subcommand. 17 | */ 18 | function do_self_update(subcmd, opts, args, cb) { 19 | var self = this; 20 | 21 | if (opts.help) { 22 | this.do_help('help', {}, [subcmd], cb); 23 | return; 24 | } 25 | 26 | var image = (opts.latest) ? 'latest' : args.shift(); 27 | 28 | if (!image) { 29 | cb(new errors.UsageError( 30 | 'Please provide an image UUID or use `sdcadm self-update --latest`\n' + 31 | 'in order to update to the latest available image.')); 32 | return; 33 | } 34 | 35 | if (image === 'help') { 36 | cb(new errors.UsageError( 37 | 'Please use `sdcadm help self-update` instead')); 38 | return; 39 | } 40 | 41 | vasync.pipeline({funcs: [ 42 | function ensureSdcApp(_, next) { 43 | self.sdcadm.ensureSdcApp({}, next); 44 | }, 45 | function setServer(_, next) { 46 | if (opts.source) { 47 | self.sdcadm.config.updatesServerUrl = opts.source; 48 | } 49 | next(); 50 | }, 51 | function setChannel(_, next) { 52 | // Set or override the default channel if anything is given: 53 | if (opts.channel) { 54 | self.sdcadm.updates.channel = opts.channel; 55 | } 56 | next(); 57 | }, 58 | function doSelfUpdate(_, next) { 59 | self.sdcadm.selfUpdate({ 60 | progress: self.progress, 61 | allowMajorUpdate: opts.allow_major_update, 62 | dryRun: opts.dry_run, 63 | image: image 64 | }, next); 65 | } 66 | ]}, cb); 67 | 68 | } 69 | 70 | do_self_update.options = [ 71 | { 72 | names: ['help', 'h'], 73 | type: 'bool', 74 | help: 'Show this help.' 75 | }, 76 | { 77 | names: ['dry-run', 'n'], 78 | type: 'bool', 79 | help: 'Go through the motions without actually updating.' 80 | }, 81 | { 82 | names: ['allow-major-update'], 83 | type: 'bool', 84 | help: 'Allow a major version update to sdcadm. By default major ' + 85 | 'updates are skipped (to avoid accidental backward ' + 86 | 'compatibility breakage).' 87 | }, 88 | { 89 | names: ['channel', 'C'], 90 | type: 'string', 91 | help: 'Use the given channel to fetch the image, even if it is ' + 92 | 'not the default one.' 93 | }, 94 | { 95 | names: ['source', 'S'], 96 | type: 'string', 97 | help: 'An image source (url) from which to import.' 98 | }, 99 | { 100 | names: ['latest'], 101 | type: 'bool', 102 | help: 'Get the latest available image.' 103 | } 104 | ]; 105 | 106 | do_self_update.help = ( 107 | 'Update "sdcadm" itself.\n' + 108 | '\n' + 109 | 'Usage:\n' + 110 | ' # Update to the given image UUID:\n' + 111 | ' {{name}} self-update IMAGE_UUID []\n' + 112 | ' # Update to the latest available image:\n' + 113 | ' {{name}} self-update --latest []\n' + 114 | ' # Update from an imgapi server:\n' + 115 | ' {{name}} self-update -S http://imgapi.dc.example.com IMAGE_UUID\n' + 116 | '\n' + 117 | '{{options}}' 118 | ); 119 | 120 | do_self_update.logToFile = true; 121 | 122 | // --- exports 123 | 124 | module.exports = { 125 | do_self_update: do_self_update 126 | }; 127 | -------------------------------------------------------------------------------- /lib/cli/do_services.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | var tabula = require('tabula'); 12 | 13 | var errors = require('../errors'); 14 | 15 | /* 16 | * The 'sdcadm services (svcs)' CLI subcommand. 17 | */ 18 | 19 | function do_services(subcmd, opts, args, callback) { 20 | var self = this; 21 | if (opts.help) { 22 | this.do_help('help', {}, [subcmd], callback); 23 | return; 24 | } else if (args.length !== 0) { 25 | callback(new errors.UsageError('too many args: ' + args)); 26 | return; 27 | } 28 | 29 | var i; 30 | var columns = opts.o.trim().split(/\s*,\s*/g); 31 | var sort = opts.s.trim().split(/\s*,\s*/g); 32 | var needInsts = opts.json || ~columns.indexOf('insts'); 33 | 34 | function getInstsIfNecessary(next) { 35 | if (!needInsts) { 36 | next(); 37 | return; 38 | } 39 | self.sdcadm.listInsts(next); 40 | } 41 | 42 | getInstsIfNecessary(function (iErr, insts) { 43 | if (iErr) { 44 | callback(iErr); 45 | return; 46 | } 47 | self.sdcadm.getServices({}, function (err, svcs) { 48 | if (err) { 49 | callback(err); 50 | return; 51 | } 52 | 53 | if (needInsts) { 54 | var countFromSvcName = {}; 55 | for (i = 0; i < insts.length; i++) { 56 | var svcName = insts[i].service; 57 | if (countFromSvcName[svcName] === undefined) { 58 | countFromSvcName[svcName] = 1; 59 | } else { 60 | countFromSvcName[svcName]++; 61 | } 62 | } 63 | for (i = 0; i < svcs.length; i++) { 64 | svcs[i].insts = countFromSvcName[svcs[i].name] || 0; 65 | } 66 | } 67 | 68 | if (opts.json) { 69 | console.log(JSON.stringify(svcs, null, 4)); 70 | } else { 71 | var validFieldsMap = {}; 72 | var rows = svcs.map(function (svc) { 73 | if (svc.type === 'vm') { 74 | return { 75 | type: svc.type, 76 | uuid: svc.uuid, 77 | name: svc.name, 78 | image: svc.params && svc.params.image_uuid, 79 | insts: svc.insts 80 | }; 81 | } else if (svc.type === 'agent') { 82 | return { 83 | type: svc.type, 84 | uuid: svc.uuid, 85 | name: svc.name, 86 | image: null, 87 | insts: svc.insts 88 | }; 89 | } else { 90 | self.log.warn({svc: svc}, 'unknown service type'); 91 | return undefined; 92 | } 93 | }).filter(function (svc) { 94 | // Filter out `undefined` entries. 95 | return svc; 96 | }); 97 | rows.forEach(function (v) { 98 | for (var k in v) { 99 | validFieldsMap[k] = true; 100 | } 101 | }); 102 | tabula(rows, { 103 | skipHeader: opts.H, 104 | columns: columns, 105 | sort: sort, 106 | validFields: Object.keys(validFieldsMap) 107 | }); 108 | } 109 | callback(); 110 | }); 111 | }); 112 | } 113 | do_services.options = [ 114 | { 115 | names: ['help', 'h'], 116 | type: 'bool', 117 | help: 'Show this help.' 118 | }, 119 | { 120 | names: ['json', 'j'], 121 | type: 'bool', 122 | help: 'JSON output' 123 | }, 124 | { 125 | names: ['H'], 126 | type: 'bool', 127 | help: 'Omit table header row.' 128 | }, 129 | { 130 | names: ['o'], 131 | type: 'string', 132 | default: 'type,uuid,name,image,insts', 133 | help: 'Specify fields (columns) to output.', 134 | helpArg: 'field1,...' 135 | }, 136 | { 137 | names: ['s'], 138 | type: 'string', 139 | default: '-type,name', 140 | help: 'Sort on the given fields. Default is "-type,name".', 141 | helpArg: 'field1,...' 142 | } 143 | ]; 144 | do_services.aliases = ['svcs']; 145 | do_services.help = ( 146 | 'List all SDC services.\n' 147 | + '\n' 148 | + 'Usage:\n' 149 | + ' {{name}} services []\n' 150 | + '\n' 151 | + '{{options}}' 152 | ); 153 | 154 | // --- exports 155 | 156 | module.exports = { 157 | do_services: do_services 158 | }; 159 | -------------------------------------------------------------------------------- /lib/cli/do_update_docker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018, Joyent, Inc. 9 | * 10 | * sdcadm experimental update-docker 11 | * 12 | * This was DEPRECATED, ... well eviscerated, in TOOLS-1438 and replaced 13 | * with 'sdcadm post-setup docker'. This will now warn and then run the 14 | * new command. 15 | */ 16 | 17 | function do_update_docker(subcmd, opts, _args, cb) { 18 | if (opts.help) { 19 | this.do_help('help', {}, [subcmd], cb); 20 | return; 21 | } 22 | 23 | console.error( 24 | '* * *\n' 25 | + '"sdcadm experimental update-docker ..." has been replaced by\n' 26 | + '"sdcadm post-setup docker". Running the new command now.\n' 27 | + 'Please update your scripts.\n' 28 | + '* * *\n'); 29 | 30 | var argv = ['', '', 'docker']; 31 | if (opts.force) { 32 | argv.push('-f'); 33 | } 34 | this.top.handlerFromSubcmd('post-setup').main(argv, cb); 35 | } 36 | 37 | do_update_docker.options = [ 38 | { 39 | names: ['help', 'h'], 40 | type: 'bool', 41 | help: 'Show this help.' 42 | }, 43 | { 44 | names: ['force', 'f'], 45 | type: 'bool', 46 | help: 'Allow update to proceed even if already at latest image.' 47 | }, 48 | { 49 | names: ['servers'], 50 | helpArg: 'SERVERS', 51 | type: 'arrayOfString', 52 | help: 'Ignored. Here for backward compatiblity.' 53 | } 54 | ]; 55 | do_update_docker.help = 56 | 'This command has been replaced by `sdcadm post-setup docker`.'; 57 | 58 | do_update_docker.logToFile = true; 59 | 60 | module.exports = { 61 | do_update_docker: do_update_docker 62 | }; 63 | -------------------------------------------------------------------------------- /lib/cli/experimental.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * Collecting 'sdcadm experimental ...' CLI commands. 13 | * 14 | * These are temporary, unsupported commands for running SDC updates before 15 | * the grand plan of 'sdcadm update' fully handling updates is complete. 16 | */ 17 | 18 | var util = require('util'); 19 | 20 | var cmdln = require('cmdln'); 21 | var Cmdln = cmdln.Cmdln; 22 | 23 | var DCMaintCLI = require('../dc-maint').DCMaintCLI; 24 | 25 | 26 | // --- Experimental CLI class 27 | 28 | function ExperimentalCLI(top) { 29 | this.top = top; 30 | Cmdln.call(this, { 31 | name: 'sdcadm experimental', 32 | desc: 'Experimental, unsupported, temporary sdcadm commands.\n' + 33 | '\n' + 34 | 'These are unsupported and temporary commands to assist with\n' + 35 | 'migration away from incr-upgrade scripts. The eventual\n' + 36 | 'general upgrade process will not include any commands under\n' + 37 | '"sdcadm experimental".', 38 | helpOpts: { 39 | minHelpCol: 27 /* line up with option help */ 40 | } 41 | }); 42 | } 43 | util.inherits(ExperimentalCLI, Cmdln); 44 | 45 | ExperimentalCLI.prototype.init = function init(_opts, _args, _callback) { 46 | this.sdcadm = this.top.sdcadm; 47 | this.progress = this.top.progress; // Deprecated. Use `ui`. 48 | this.ui = this.top.ui; 49 | this.log = this.top.log; 50 | 51 | Cmdln.prototype.init.apply(this, arguments); 52 | }; 53 | 54 | ExperimentalCLI.hidden = true; 55 | 56 | 57 | ExperimentalCLI.prototype.do_info = require('./do_info').do_info; 58 | 59 | 60 | ExperimentalCLI.prototype.do_update_agents = 61 | require('./do_update_agents').do_update_agents; 62 | 63 | // TOOLS-905: This is deprecated, the command has been moved 64 | // out of experimental and it's just here to warn users about 65 | // that fact. Remove after one month since Nov. the 25th. 66 | ExperimentalCLI.prototype.do_dc_maint = DCMaintCLI; 67 | 68 | ExperimentalCLI.prototype.do_update_other = 69 | require('./do_update_other').do_update_other; 70 | 71 | 72 | ExperimentalCLI.prototype.do_update_gz_tools = 73 | require('./do_update_gz_tools').do_update_gz_tools; 74 | 75 | 76 | ExperimentalCLI.prototype.do_add_new_agent_svcs = 77 | require('./do_add_new_agent_svcs').do_add_new_agent_svcs; 78 | 79 | 80 | ExperimentalCLI.prototype.do_update_docker = 81 | require('./do_update_docker').do_update_docker; 82 | 83 | ExperimentalCLI.prototype.do_install_docker_cert = 84 | require('./do_install_docker_cert').do_install_docker_cert; 85 | 86 | ExperimentalCLI.prototype.do_fix_core_vm_resolvers = 87 | require('./do_fix_core_vm_resolvers').do_fix_core_vm_resolvers; 88 | 89 | // Deprecated: TOOLS-1667 90 | ExperimentalCLI.prototype.do_cns = require('../post-setup/cns').do_cns; 91 | 92 | ExperimentalCLI.prototype.do_nfs_volumes = 93 | require('./do_nfs_volumes').do_nfs_volumes; 94 | 95 | ExperimentalCLI.prototype.do_remove_ca = require('./do_remove_ca').do_remove_ca; 96 | 97 | // --- exports 98 | 99 | module.exports = { 100 | ExperimentalCLI: ExperimentalCLI 101 | }; 102 | // vim: set softtabstop=4 shiftwidth=4: 103 | -------------------------------------------------------------------------------- /lib/cli/ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * A class to capture all/most output on the CLI. Typically the sdcadm CLI 13 | * will have an instanced of this at `this.ui`. Sdcadm code should use that 14 | * to write output for the user. 15 | * 16 | * Backwards compat: Not all sdcadm code has been migrated to use this. There 17 | * is still a lot of usage of a `progress` function and `ProgressBar`s. 18 | * 19 | * Usage in sdcadm code: 20 | * 21 | * - get the `.ui` object 22 | * - `ui.info(...)` for printf-style message output to stdout 23 | * - `ui.error(...)` for printf-style error message output to stdout. If on 24 | * a TTY, this is colored red. Otherwise it is the same as `ui.info`. 25 | * - To use a progress bar:kkkkk 26 | * - call `ui.barStart({name: 'NAME', size: SIZE})` 27 | * - call `ui.barAdvance(N)` to advance progress 28 | * - call `ui.barEnd()` when complete. 29 | * These methods know to avoid using a progress bar if output is not to a 30 | * TTY. `ui.info` and `ui.error` know to use `.log` when a progress bar 31 | * is active. 32 | */ 33 | 34 | 'use strict'; 35 | 36 | var format = require('util').format; 37 | 38 | var assert = require('assert-plus'); 39 | var ProgressBar = require('progbar').ProgressBar; 40 | var VError = require('verror'); 41 | 42 | 43 | // ---- internal support stuff 44 | 45 | // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics 46 | // Suggested colors (some are unreadable in common cases): 47 | // - Good: cyan, yellow (limited use), bold, green, magenta, red 48 | // - Bad: grey (same color as background on Solarized Dark theme from 49 | // , see issue #160) 50 | var colors = { 51 | 'bold': [1, 22], 52 | 'italic': [3, 23], 53 | 'underline': [4, 24], 54 | 'inverse': [7, 27], 55 | 'white': [37, 39], 56 | 'grey': [90, 39], 57 | 'black': [30, 39], 58 | 'blue': [34, 39], 59 | 'cyan': [36, 39], 60 | 'green': [32, 39], 61 | 'magenta': [35, 39], 62 | 'red': [31, 39], 63 | 'yellow': [33, 39] 64 | }; 65 | 66 | function stylizeWithColor(str, color) { 67 | if (!str) 68 | return ''; 69 | var codes = colors[color]; 70 | if (codes) { 71 | return '\x1b[' + codes[0] + 'm' + str + '\x1b[' + codes[1] + 'm'; 72 | } else { 73 | return str; 74 | } 75 | } 76 | 77 | function stylizeWithoutColor(str, _color) { 78 | return str; 79 | } 80 | 81 | 82 | 83 | // ---- UI 84 | 85 | function UI(opts) { 86 | assert.object(opts.log, 'opts.log'); 87 | assert.optionalBool(opts.color, 'opts.color'); 88 | 89 | this.log = opts.log.child({ui: true}, true); 90 | 91 | // We support ANSI escape code coloring (currently just used for `ui.error`) 92 | // if writing to a TTY. Use `SDCADM_NO_COLOR=1` envvar to disable. 93 | var color = opts.color; 94 | if (color === null || color === undefined) { 95 | if (process.env.SDCADM_NO_COLOR && 96 | process.env.SDCADM_NO_COLOR.length > 0) { 97 | color = false; 98 | } else { 99 | color = process.stdout.isTTY; 100 | } 101 | } 102 | this._stylize = (color ? stylizeWithColor : stylizeWithoutColor); 103 | } 104 | 105 | // Temporary convenience function for parts of sdcadm that still use the 106 | // old `progress` function for emitting text to the user. 107 | UI.prototype.progressFunc = function progressFunc() { 108 | return this.info.bind(this); 109 | }; 110 | 111 | UI.prototype.info = function info() { 112 | var msgArgs = Array.prototype.slice.call(arguments); 113 | var msg = format.apply(null, msgArgs); 114 | this.log.debug(msg); 115 | if (this._bar) { 116 | this._bar.log(msg); 117 | } else { 118 | console.log(msg); 119 | } 120 | }; 121 | 122 | UI.prototype.error = function error() { 123 | var msgArgs = Array.prototype.slice.call(arguments); 124 | var msg = format.apply(null, msgArgs); 125 | this.log.debug(msg); 126 | var styled = this._stylize(msg, 'red'); 127 | if (this._bar) { 128 | this._bar.log(styled); 129 | } else { 130 | console.log(styled); 131 | } 132 | }; 133 | 134 | // Start a progress bar. 135 | // 136 | // This will be a no-op for cases where a progress bar is inappropriate 137 | // (e.g. if stderr is not a TTY). 138 | UI.prototype.barStart = function barStart(opts) { 139 | assert.string(opts.name, 'opts.name'); 140 | assert.finite(opts.size, 'opts.size'); 141 | 142 | if (this._bar) { 143 | throw new VError('another progress bar (%s) is currently active', 144 | this._bar.filename); 145 | } else if (process.stderr.isTTY) { 146 | this._bar = new ProgressBar({ 147 | filename: opts.name, 148 | size: opts.size, 149 | // ProgressBar began life assuming it was progress for a file 150 | // download. Hence `*file*name`. To avoid it appending size suffixes 151 | // like "KB" and "MB" we need to explicitly `bytes: false`. 152 | bytes: false 153 | }); 154 | this._bar.draw(); 155 | } 156 | }; 157 | 158 | UI.prototype.barEnd = function barEnd() { 159 | if (this._bar) { 160 | this._bar.end(); 161 | delete this._bar; 162 | } 163 | }; 164 | 165 | UI.prototype.barAdvance = function barAdvance(n) { 166 | if (this._bar) { 167 | this._bar.advance(n); 168 | } 169 | }; 170 | 171 | 172 | // --- Mock UI 173 | 174 | // Create a mock `UI` instance. The optional `opts.write` allows, for example, 175 | // a test to do: 176 | // 177 | // ui = new MockUI({write: tap.comment}); 178 | // 179 | function MockUI(opts) { 180 | assert.optionalFunc(opts.write, 'opts.write'); 181 | this._write = opts.write || console.log; 182 | } 183 | 184 | MockUI.prototype.progressFunc = function progressFunc() { 185 | return this.info.bind(this); 186 | }; 187 | 188 | MockUI.prototype.info = function info() { 189 | var msgArgs = Array.prototype.slice.call(arguments); 190 | var msg = format.apply(null, msgArgs); 191 | this._write(msg); 192 | }; 193 | 194 | MockUI.prototype.error = function error() { 195 | var msgArgs = Array.prototype.slice.call(arguments); 196 | var msg = format.apply(null, msgArgs); 197 | this._write(msg); 198 | }; 199 | 200 | MockUI.prototype.barStart = function mockBarStart(_opts) {}; 201 | MockUI.prototype.barEnd = function mockBarEnd() {}; 202 | MockUI.prototype.barAdvance = function mockBarAdvance(_n) {}; 203 | 204 | 205 | // --- exports 206 | 207 | module.exports = { 208 | UI: UI, 209 | MockUI: MockUI 210 | }; 211 | -------------------------------------------------------------------------------- /lib/default-fabric.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * Support for adding a default fabric for an account, including 13 | * `sdcadm experimental default-fabric ...`. 14 | */ 15 | 16 | var assert = require('assert-plus'); 17 | var common = require('./common'); 18 | var errors = require('./errors'); 19 | var vasync = require('vasync'); 20 | 21 | 22 | 23 | // ---- internal support functions 24 | 25 | function defFabricAddVLAN(opts, cb) { 26 | // XXX: this should be stored in the fabrics cfg object 27 | var vlanCfg = { 28 | name: 'default', 29 | vlan_id: 2 30 | }; 31 | 32 | var napi = opts.sdcadm.napi; 33 | napi.listFabricVLANs(opts.account, {}, function (listErr, vlans) { 34 | if (listErr) { 35 | cb(listErr); 36 | return; 37 | } 38 | for (var i = 0; i < vlans.length; i++) { 39 | if (vlans[i].name === vlanCfg.name) { 40 | opts.progress('Already have default fabric VLAN for account %s', 41 | opts.account); 42 | cb(null, vlans[i]); 43 | return; 44 | } 45 | } 46 | napi.createFabricVLAN(opts.account, vlanCfg, function (err, vlan) { 47 | if (err) { 48 | cb(new errors.SDCClientError(err, 'napi')); 49 | return; 50 | } 51 | opts.progress('Created default fabric VLAN\n' + 52 | '(name: "%s", ID: %d)\nfor account %s', 53 | vlan.name, vlan.vlan_id, opts.account); 54 | cb(null, vlan); 55 | }); 56 | }); 57 | 58 | } 59 | 60 | 61 | function defFabricAddNetwork(opts, cb) { 62 | // XXX: this should be stored in the fabrics cfg object 63 | var netCfg = { 64 | name: 'default', 65 | subnet: '192.168.128.0/22', 66 | provision_start_ip: '192.168.128.5', 67 | provision_end_ip: '192.168.131.250', 68 | gateway: '192.168.128.1', 69 | resolvers: opts.sdcadm.sdcApp.metadata.dns_resolvers.split(','), 70 | vlan_id: 2 71 | }; 72 | 73 | var napi = opts.sdcadm.napi; 74 | napi.listFabricNetworks(opts.account, netCfg.vlan_id, {}, 75 | function (listErr, nets) { 76 | if (listErr) { 77 | cb(listErr); 78 | return; 79 | } 80 | for (var i = 0; i < nets.length; i++) { 81 | if (nets[i].name === netCfg.name) { 82 | opts.progress( 83 | 'Already have default fabric network for account %s', 84 | opts.account); 85 | cb(null, nets[i]); 86 | return; 87 | } 88 | } 89 | napi.createFabricNetwork(opts.account, netCfg.vlan_id, netCfg, 90 | function (err, net) { 91 | if (err) { 92 | cb(new errors.SDCClientError(err, 'napi')); 93 | return; 94 | } 95 | opts.progress('Created default fabric network\n' + 96 | '(subnet: %s, ID: %s)\nfor account %s', 97 | net.subnet, net.uuid, opts.account); 98 | cb(null, net); 99 | }); 100 | }); 101 | } 102 | 103 | 104 | 105 | // ---- exports 106 | 107 | /** 108 | * Add a default fabric for the given account. 109 | */ 110 | function addDefaultFabric(opts, cb) { 111 | assert.object(opts, 'opts'); 112 | assert.uuid(opts.account, 'opts.account'); 113 | assert.object(opts.sdcadm, 'opts.sdcadm'); 114 | assert.func(opts.progress, 'opts.progress'); 115 | assert.func(cb, 'cb'); 116 | 117 | vasync.pipeline({ arg: opts, funcs: [ 118 | function ensureSdcApp(_, next) { 119 | opts.sdcadm.ensureSdcApp({}, next); 120 | }, 121 | defFabricAddVLAN, 122 | defFabricAddNetwork 123 | ]}, function (err, results) { 124 | if (err) { 125 | cb(err); 126 | return; 127 | } 128 | opts.progress('Successfully added default fabric for account %s', 129 | opts.account); 130 | cb(); 131 | }); 132 | } 133 | 134 | 135 | function do_default_fabric(subcmd, opts, args, cb) { 136 | var self = this; 137 | if (opts.help === true) { 138 | this.do_help('help', {}, [ subcmd ], cb); 139 | return; 140 | } else if (args.length !== 1) { 141 | cb(new errors.UsageError('Must specify account')); 142 | return; 143 | } 144 | 145 | vasync.pipeline({arg: {}, funcs: [ 146 | function ensureAccountUuid(ctx, next) { 147 | if (common.UUID_RE.test(args[0])) { 148 | ctx.account = args[0]; 149 | next(); 150 | return; 151 | } 152 | self.sdcadm.ufds.getUser(args[0], function (err, account) { 153 | if (err) { 154 | next(err); 155 | return; 156 | } 157 | ctx.account = account.uuid; 158 | self.sdcadm._ufds.close(next); // Yuck 159 | }); 160 | }, 161 | function addIt(ctx, next) { 162 | var addOpts = { 163 | account: ctx.account, 164 | sdcadm: self.sdcadm, 165 | progress: self.progress 166 | }; 167 | addDefaultFabric(addOpts, next); 168 | } 169 | ]}, cb); 170 | } 171 | 172 | do_default_fabric.help = 'Initialize a default fabric for an account.\n' + 173 | '\n' + 174 | 'Usage: {{name}} default-fabric [-h] \n' + 175 | '\n' + 176 | '{{options}}'; 177 | 178 | do_default_fabric.options = [ 179 | { 180 | names: [ 'help', 'h' ], 181 | type: 'bool', 182 | help: 'Display this help message' 183 | } 184 | ]; 185 | 186 | do_default_fabric.logToFile = true; 187 | 188 | 189 | module.exports = { 190 | do_default_fabric: do_default_fabric, 191 | addDefaultFabric: addDefaultFabric 192 | }; 193 | -------------------------------------------------------------------------------- /lib/post-setup/cloudapi.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * 'sdcadm post-setup cloudapi' 13 | */ 14 | 15 | var assert = require('assert-plus'); 16 | var vasync = require('vasync'); 17 | 18 | var common = require('../common'); 19 | var errors = require('../errors'); 20 | var svcadm = require('../svcadm'); 21 | 22 | // --- internal support stuff 23 | 24 | 25 | function createCloudapiInstance(opts, callback) { 26 | assert.object(opts, 'opts'); 27 | assert.object(opts.sdcadm, 'opts.sdcadm'); 28 | assert.func(opts.progress, 'opts.progress'); 29 | assert.func(callback, 'callback'); 30 | 31 | var progress = opts.progress; 32 | var sdcadm = opts.sdcadm; 33 | var sapi = opts.sdcadm.sapi; 34 | 35 | var cloudapisvc; 36 | 37 | // find cloudapi service, get service uuid 38 | // use sapi.createInstance to create the service 39 | 40 | vasync.pipeline({ arg: {}, funcs: [ 41 | function (_, next) { 42 | sapi.listServices({ name: 'cloudapi' }, function (err, svcs) { 43 | if (err) { 44 | next(new errors.SDCClientError(err, 'sapi')); 45 | return; 46 | } 47 | if (svcs.length !== 1) { 48 | next(new Error('expected 1 cloudapi service, found %d', 49 | svcs.length)); 50 | return; 51 | } 52 | cloudapisvc = svcs[0]; 53 | next(); 54 | }); 55 | }, 56 | function getHeadnode(ctx, next) { 57 | sdcadm.getCurrServerUuid(function (err, hn) { 58 | if (err) { 59 | next(err); 60 | return; 61 | } 62 | ctx.headnodeUuid = hn; 63 | next(); 64 | }); 65 | }, 66 | function (ctx, next) { 67 | var cOpts = { 68 | params: { 69 | server_uuid: ctx.headnodeUuid, 70 | alias: opts.alias 71 | } 72 | }; 73 | sapi.createInstance(cloudapisvc.uuid, cOpts, function (err, _inst) { 74 | if (err) { 75 | next(new errors.SDCClientError(err, 'sapi')); 76 | return; 77 | } 78 | next(); 79 | }); 80 | }, 81 | function hupHermes(_, next) { 82 | svcadm.restartHermes({ 83 | sdcadm: sdcadm, 84 | log: sdcadm.log, 85 | progress: progress 86 | }, next); 87 | } 88 | ] }, function (err) { 89 | if (!err) { 90 | progress('cloudapi0 zone created'); 91 | } 92 | callback(err); 93 | }); 94 | } 95 | 96 | function Cloudapi() {} 97 | 98 | Cloudapi.prototype.name = 'cloudapi'; 99 | Cloudapi.prototype.help = ( 100 | 'Create a first cloudapi instance.\n' + 101 | '\n' + 102 | 'Initial setup of SmartDataCenter does not create a cloudapi instance.\n' + 103 | 'This procedure will do that for you.\n' 104 | ); 105 | Cloudapi.prototype.execute = function cExecute(options, cb) { 106 | assert.object(options, 'options'); 107 | assert.object(options.sdcadm, 'options.sdcadm'); 108 | assert.object(options.log, 'options.log'); 109 | assert.func(options.progress, 'options.progress'); 110 | assert.func(cb, 'cb'); 111 | 112 | var log = options.log; 113 | var sdcadm = options.sdcadm; 114 | var progress = options.progress; 115 | 116 | function onInstances(err, insts) { 117 | if (err) { 118 | cb(err); 119 | return; 120 | } 121 | 122 | log.info({insts: insts}, '%d existing cloudapi insts', insts.length); 123 | if (insts.length === 1) { 124 | progress('Already have a cloudapi: vm %s (%s)', 125 | insts[0].instance, insts[0].alias); 126 | cb(); 127 | return; 128 | } else if (insts.length > 1) { 129 | progress('Already have %d cloudapi instances: vm %s (%s), ...', 130 | insts.length, insts[0].instance, insts[0].alias); 131 | cb(); 132 | return; 133 | } 134 | 135 | createCloudapiInstance({ 136 | alias: 'cloudapi0', 137 | progress: progress, 138 | sdcadm: sdcadm 139 | }, cb); 140 | } 141 | 142 | sdcadm.listInsts({svcs: ['cloudapi']}, onInstances); 143 | }; 144 | 145 | 146 | 147 | // --- CLI 148 | 149 | function do_cloudapi(subcmd, opts, args, cb) { 150 | var self = this; 151 | if (opts.help) { 152 | this.do_help('help', {}, [subcmd], cb); 153 | return; 154 | } else if (args.length > 0) { 155 | cb(new errors.UsageError('too many args: ' + args)); 156 | return; 157 | } 158 | 159 | var proc = new Cloudapi(); 160 | 161 | function setupCloudapi(options, callback) { 162 | proc.execute(options, callback); 163 | } 164 | 165 | function setupCloudapiCb(err) { 166 | if (err) { 167 | self.top.progress('CloudAPI setup failed'); 168 | cb(err); 169 | return; 170 | } 171 | cb(); 172 | } 173 | 174 | common.execWithRetries({ 175 | func: setupCloudapi, 176 | cb: setupCloudapiCb, 177 | args: { 178 | sdcadm: this.sdcadm, 179 | log: this.log.child({postSetup: 'cloudapi'}, true), 180 | progress: self.top.progress 181 | }, 182 | log: self.log, 183 | retries: opts.retries 184 | }); 185 | 186 | 187 | } 188 | 189 | do_cloudapi.help = ( 190 | Cloudapi.prototype.help + 191 | '\n' + 192 | 'Usage:\n' + 193 | ' {{name}} cloudapi\n' 194 | ); 195 | 196 | do_cloudapi.options = [ 197 | { 198 | names: ['help', 'h'], 199 | type: 'bool', 200 | help: 'Show this help.' 201 | } 202 | ]; 203 | 204 | do_cloudapi.logToFile = true; 205 | 206 | 207 | // --- exports 208 | 209 | module.exports = { 210 | do_cloudapi: do_cloudapi 211 | }; 212 | -------------------------------------------------------------------------------- /lib/post-setup/common-external-nics.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2022 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * 'sdcadm post-setup common-external-nics' 14 | */ 15 | 16 | var errors = require('../errors'); 17 | var EnsureNicProc = require('../procedures/ensure-nic-on-instances') 18 | .EnsureNicOnInstancesProcedure; 19 | var runProcs = require('../procedures').runProcs; 20 | 21 | function do_common_external_nics(subcmd, opts, args, cb) { 22 | var self = this; 23 | if (opts.help) { 24 | this.do_help('help', {}, [subcmd], cb); 25 | return; 26 | } else if (args.length > 0) { 27 | cb(new errors.UsageError('too many args: ' + args)); 28 | return; 29 | } 30 | 31 | const ensureNicProc = new EnsureNicProc({ 32 | svcNames: ['imgapi', 'adminui'], 33 | nicTag: 'external', 34 | primary: true, 35 | hardFail: true, 36 | volatile: false 37 | }); 38 | 39 | runProcs({ 40 | log: self.log, 41 | procs: [ensureNicProc], 42 | sdcadm: self.sdcadm, 43 | ui: self.ui, 44 | /* 45 | * Before the introduction of the procedure framework, `post-setup 46 | * common-external-nics` did not prompt the user for confirmation. 47 | * Scripts that run `post-setup common-external-nics` rely on this 48 | * behavior, so we hard-code 'skipConfirm' to 'true' to simulate it. 49 | * 50 | * This may change in the future -- a longer-term solution is to add 51 | * a -y/--yes flag to `post-setup common-external-nics` that skips 52 | * confirmation, and then update all of the scripts that run this 53 | * procedure to use the new flag. 54 | */ 55 | skipConfirm: true 56 | }, cb); 57 | } 58 | 59 | do_common_external_nics.help = ( 60 | 'Add external NICs to the adminui and imgapi zones.\n' + 61 | '\n' + 62 | 'By default no SDC core zones are given external nics in initial\n' + 63 | 'setup. Typically it is most useful to have those for the adminui\n' + 64 | 'instance (to be able to access the operator portal in your browser)\n' + 65 | 'and for the imgapi instance (to enable it to reach out to\n' + 66 | 'updates.tritondatacenter.com and images.smartos.org for images).\n' + 67 | 'IMGAPI instances are always firewalled such that only outbound\n' + 68 | 'connections are allowed.\n' + 69 | '\n' + 70 | 'Usage:\n' + 71 | ' {{name}} common-external-nics\n' 72 | ); 73 | 74 | do_common_external_nics.options = [ 75 | { 76 | names: ['help', 'h'], 77 | type: 'bool', 78 | help: 'Show this help.' 79 | } 80 | ]; 81 | 82 | do_common_external_nics.logToFile = true; 83 | 84 | // --- exports 85 | 86 | module.exports = { 87 | do_common_external_nics: do_common_external_nics 88 | }; 89 | -------------------------------------------------------------------------------- /lib/post-setup/dev-headnode-prov.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2024 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * 'sdcadm post-setup dev-headnode-prov' 14 | */ 15 | 16 | var assert = require('assert-plus'); 17 | var vasync = require('vasync'); 18 | 19 | var errors = require('../errors'); 20 | var svcadm = require('../svcadm'); 21 | 22 | 23 | // --- internal support stuff 24 | 25 | function makeHeadnodeProvisionable(opts, cb) { 26 | assert.object(opts, 'opts'); 27 | assert.object(opts.sdcadm, 'opts.sdcadm'); 28 | assert.object(opts.log, 'opts.log'); 29 | assert.func(opts.progress, 'opts.progress'); 30 | assert.func(cb, 'cb'); 31 | 32 | var log = opts.log; 33 | var sdcadm = opts.sdcadm; 34 | var progress = opts.progress; 35 | 36 | vasync.pipeline({arg: {}, funcs: [ 37 | function getCnapiSvc(ctx, next) { 38 | sdcadm.getSvc({app: 'sdc', svc: 'cnapi'}, function (err, cnapi) { 39 | if (err) { 40 | next(err); 41 | return; 42 | } 43 | ctx.cnapi = cnapi; 44 | next(); 45 | }); 46 | }, 47 | function tweakCnapiConfig(ctx, next) { 48 | if (ctx.cnapi.metadata.ALLOC_FILTER_CAPNESS === false && 49 | ctx.cnapi.metadata.ALLOC_FILTER_HEADNODE === false && 50 | ctx.cnapi.metadata.ALLOC_FILTER_MIN_RESOURCES === false) { 51 | progress('CNAPI is already configured to allow headnode ' + 52 | 'provisioning and over-provisioning'); 53 | ctx.alreadyDone = true; 54 | next(); 55 | return; 56 | } 57 | 58 | progress('Configuring CNAPI to allow headnode provisioning' + 59 | ' and over-provisioning'); 60 | var update = { 61 | metadata: { 62 | ALLOC_FILTER_CAPNESS: false, 63 | ALLOC_FILTER_HEADNODE: false, 64 | ALLOC_FILTER_MIN_RESOURCES: false 65 | } 66 | }; 67 | sdcadm.sapi.updateService(ctx.cnapi.uuid, update, 68 | errors.sdcClientErrWrap(next, 'sapi')); 69 | }, 70 | 71 | function getCnapiInsts(ctx, next) { 72 | if (ctx.alreadyDone) { 73 | next(); 74 | return; 75 | } 76 | 77 | var listOpts = {types: ['vm'], svcs: ['cnapi']}; 78 | sdcadm.listInsts(listOpts, function (err, insts) { 79 | if (err) { 80 | next(err); 81 | return; 82 | } 83 | ctx.cnapiInsts = insts; 84 | log.trace({cnapiInsts: ctx.cnapiInsts}, 'cnapiInsts'); 85 | next(); 86 | }); 87 | }, 88 | 89 | function updatesCnapiInsts(ctx, next) { 90 | if (ctx.alreadyDone) { 91 | next(); 92 | return; 93 | } 94 | 95 | var queue = vasync.queue( 96 | function refreshCnapiInst(inst, nextInst) { 97 | progress('Refreshing instance %s config-agent', inst.alias); 98 | svcadm.svcadmRefresh({ 99 | server_uuid: inst.server, 100 | zone: inst.instance, 101 | wait: true, 102 | fmri: 'config-agent', 103 | sdcadm: sdcadm, 104 | log: log 105 | }, nextInst); 106 | }, 107 | 10); 108 | queue.push(ctx.cnapiInsts); 109 | queue.close(); 110 | queue.on('end', function done() { 111 | next(); 112 | }); 113 | } 114 | ]}, cb); 115 | } 116 | 117 | 118 | // --- CLI 119 | 120 | function do_dev_headnode_prov(subcmd, opts, args, cb) { 121 | if (opts.help) { 122 | this.do_help('help', {}, [subcmd], cb); 123 | return; 124 | } else if (args.length > 0) { 125 | cb(new errors.UsageError('too many args: ' + args)); 126 | return; 127 | } 128 | 129 | makeHeadnodeProvisionable({ 130 | sdcadm: this.sdcadm, 131 | log: this.log.child({postSetup: 'dev-headnode-prov'}, true), 132 | progress: this.top.progress 133 | }, cb); 134 | } 135 | 136 | do_dev_headnode_prov.help = ( 137 | 'Make the headnode provisionable, for development and testing.\n' + 138 | '\n' + 139 | 'This is done via `ALLOC_FILTER_CAPNESS`, `ALLOC_FILTER_HEADNODE`, and \n' + 140 | '`ALLOC_FILTER_MIN_RESOURCES` SAPI parameters of the CNAPI service.\n' + 141 | 'See https://github.com/TritonDataCenter/sdc-cnapi/blob/master/docs/index.md#sapi-configuration\n' + 142 | '\n' + 143 | 'Usage:\n' + 144 | ' {{name}} dev-headnode-prov\n' 145 | ); 146 | 147 | do_dev_headnode_prov.options = [ 148 | { 149 | names: ['help', 'h'], 150 | type: 'bool', 151 | help: 'Show this help.' 152 | } 153 | ]; 154 | 155 | do_dev_headnode_prov.logToFile = true; 156 | 157 | // --- exports 158 | 159 | module.exports = { 160 | do_dev_headnode_prov: do_dev_headnode_prov 161 | }; 162 | -------------------------------------------------------------------------------- /lib/post-setup/firewall-logger-agent.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2022 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * The 'sdcadm post-setup firewall-logger-agent' CLI subcommand. 14 | */ 15 | 16 | 17 | var errors = require('../errors'); 18 | var AddAgentServiceProc = 19 | require('../procedures/add-agent-service').AddAgentServiceProcedure; 20 | var runProcs = require('../procedures').runProcs; 21 | 22 | function do_firewall_logger_agent(subcmd, opts, args, cb) { 23 | var self = this; 24 | if (opts.help) { 25 | this.do_help('help', {}, [subcmd], cb); 26 | return; 27 | } else if (args.length > 0) { 28 | cb(new errors.UsageError('too many args: ' + args)); 29 | return; 30 | } 31 | 32 | const svcName = 'firewall-logger-agent'; 33 | 34 | const procOpts = { 35 | svcName: svcName, 36 | concurrency: opts.concurrency 37 | }; 38 | 39 | if (opts.image) { 40 | procOpts.image = opts.image; 41 | } 42 | 43 | if (opts.channel) { 44 | procOpts.channel = opts.channel; 45 | } 46 | 47 | if (opts.servers) { 48 | procOpts.includeServerNames = opts.servers; 49 | } 50 | 51 | if (opts.exclude_servers) { 52 | procOpts.excludeServerNames = opts.exclude_servers; 53 | } 54 | 55 | const proc = new AddAgentServiceProc(procOpts); 56 | 57 | runProcs({ 58 | log: self.log, 59 | procs: [proc], 60 | sdcadm: self.sdcadm, 61 | ui: self.ui, 62 | dryRun: opts.dry_run, 63 | skipConfirm: opts.yes 64 | }, cb); 65 | } 66 | 67 | do_firewall_logger_agent.options = [ 68 | { 69 | names: ['help', 'h'], 70 | type: 'bool', 71 | help: 'Show this help.' 72 | }, 73 | { 74 | names: ['yes', 'y'], 75 | type: 'bool', 76 | help: 'Answer yes to all confirmations.' 77 | }, 78 | { 79 | names: ['dry-run', 'n'], 80 | type: 'bool', 81 | help: 'Do a dry-run.' 82 | }, 83 | { 84 | group: 'Server selection (by default all setup servers are updated)' 85 | }, 86 | { 87 | names: ['servers', 's'], 88 | type: 'arrayOfCommaSepString', 89 | helpArg: 'NAMES', 90 | help: 'Comma-separated list of servers (either hostnames or uuids) ' + 91 | 'on which to update cn_tools.' 92 | }, 93 | { 94 | names: ['exclude-servers', 'S'], 95 | type: 'arrayOfCommaSepString', 96 | helpArg: 'NAMES', 97 | help: 'Comma-separated list of servers (either hostnames or uuids) ' + 98 | 'to exclude from cn_tools update.' 99 | }, 100 | { 101 | names: ['concurrency', 'j'], 102 | type: 'integer', 103 | 'default': 10, 104 | help: 'Number of concurrent servers ' + 105 | 'being updated simultaneously. Default: 10', 106 | helpArg: 'CONCURRENCY' 107 | }, 108 | { 109 | group: 'Image selection (by default latest image on default ' + 110 | 'channel)' 111 | }, 112 | { 113 | names: ['image', 'i'], 114 | type: 'string', 115 | help: 'Specifies which image to use for the instances. ' + 116 | 'Use "latest" (the default) for the latest available on ' + 117 | 'updates.tritondatacenter.com, "current" for the latest image ' + 118 | 'already in the datacenter (if any), or an image UUID or version.' 119 | }, 120 | { 121 | names: ['channel', 'C'], 122 | type: 'string', 123 | help: 'The updates.tritondatacenter.com channel from which to fetch ' + 124 | 'the image. See `sdcadm channel get` for the default channel.' 125 | } 126 | 127 | ]; 128 | 129 | do_firewall_logger_agent.help = [ 130 | 'Create "firewall-logger-agent" service and the required agent instances.', 131 | '', 132 | 'Usage:', 133 | ' {{name}} firewall-logger-agent', 134 | '', 135 | '{{options}}', 136 | 'The "firewall-logger-agent" service generates specific Triton log files', 137 | 'for the configured firewall rules.' 138 | ].join('\n'); 139 | 140 | do_firewall_logger_agent.logToFile = true; 141 | 142 | // --- exports 143 | 144 | module.exports = { 145 | do_firewall_logger_agent: do_firewall_logger_agent 146 | }; 147 | 148 | // vim: set softtabstop=4 shiftwidth=4: 149 | -------------------------------------------------------------------------------- /lib/post-setup/grafana.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2022 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * The 'sdcadm post-setup grafana' CLI subcommand. 14 | */ 15 | 16 | var errors = require('../errors'); 17 | var AddServiceProc = require('../procedures/add-service').AddServiceProcedure; 18 | var EnsureNicProc = require('../procedures/ensure-nic-on-instances') 19 | .EnsureNicOnInstancesProcedure; 20 | var runProcs = require('../procedures').runProcs; 21 | 22 | function do_grafana(subcmd, opts, args, cb) { 23 | var self = this; 24 | if (opts.help) { 25 | this.do_help('help', {}, [subcmd], cb); 26 | return; 27 | } else if (args.length > 0) { 28 | cb(new errors.UsageError('too many args: ' + args)); 29 | return; 30 | } 31 | 32 | const svcName = 'grafana'; 33 | const addServiceOpts = { 34 | svcName: svcName, 35 | packageName: 'sdc_1024', 36 | delegatedDataset: true, 37 | networks: [ 38 | {name: 'admin'}, 39 | /* 40 | * Grafana is on the external network to allow login access from 41 | * the public internet. 42 | */ 43 | {name: 'external', primary: true} 44 | ] 45 | }; 46 | 47 | if (opts.image) { 48 | addServiceOpts.image = opts.image; 49 | } 50 | 51 | if (opts.channel) { 52 | addServiceOpts.channel = opts.channel; 53 | } 54 | 55 | if (opts.server) { 56 | addServiceOpts.server = opts.server; 57 | } 58 | 59 | /* 60 | * We add the manta nic here, rather than in the hard-coded service json 61 | * above, because the EnsureNicOnInstancesProcedure will gracefully 62 | * handle the case where the manta network does not exist. 63 | * 64 | * If the manta network doesn't exist, the procedure will do nothing. If 65 | * `sdcadm post-setup grafana` is run later and the manta network now 66 | * exists, the procedure will add a manta nic to the existing grafana 67 | * instance. 68 | * 69 | * We set 'volatile' to true here because we're also (possibly) creating the 70 | * grafana service and instance in the same runProcs sequence, and thus 71 | * must defer lookup of the service and instance to the execute() phase. 72 | */ 73 | const ensureNicOpts = { 74 | svcNames: [ svcName ], 75 | nicTag: 'manta', 76 | primary: false, 77 | hardFail: false, 78 | volatile: true 79 | }; 80 | 81 | const addServiceProc = new AddServiceProc(addServiceOpts); 82 | const ensureNicProc = new EnsureNicProc(ensureNicOpts); 83 | runProcs({ 84 | log: self.log, 85 | procs: [addServiceProc, ensureNicProc], 86 | sdcadm: self.sdcadm, 87 | ui: self.ui, 88 | dryRun: opts.dry_run, 89 | skipConfirm: opts.yes 90 | }, cb); 91 | } 92 | 93 | do_grafana.options = [ 94 | { 95 | names: ['help', 'h'], 96 | type: 'bool', 97 | help: 'Show this help.' 98 | }, 99 | { 100 | names: ['yes', 'y'], 101 | type: 'bool', 102 | help: 'Answer yes to all confirmations.' 103 | }, 104 | { 105 | names: ['dry-run', 'n'], 106 | type: 'bool', 107 | help: 'Do a dry-run.' 108 | }, 109 | { 110 | names: ['server', 's'], 111 | type: 'string', 112 | help: 'Either hostname or uuid of the server on which to create' + 113 | ' the instance. (By default the headnode will be used.)', 114 | helpArg: 'SERVER' 115 | }, 116 | { 117 | group: 'Image selection (by default latest image on default ' + 118 | 'channel)' 119 | }, 120 | { 121 | names: ['image', 'i'], 122 | type: 'string', 123 | help: 'Specifies which image to use for the first instance. ' + 124 | 'Use "latest" (the default) for the latest available on ' + 125 | 'updates.tritondatacenter.com, "current" for the latest image ' + 126 | 'already in the datacenter (if any), or an image UUID or version.' 127 | }, 128 | { 129 | names: ['channel', 'C'], 130 | type: 'string', 131 | help: 'The updates.tritondatacenter.com channel from which to fetch ' + 132 | 'the image. See `sdcadm channel get` for the default channel.' 133 | } 134 | 135 | ]; 136 | 137 | do_grafana.help = [ 138 | 'Create the "grafana" service and a first instance.', 139 | '', 140 | 'Usage:', 141 | ' {{name}} grafana', 142 | '', 143 | '{{options}}', 144 | 'The "grafana" service provides a graphical front-end to the Prometheus ' + 145 | 'time-series database.' 146 | ].join('\n'); 147 | 148 | do_grafana.logToFile = true; 149 | 150 | // --- exports 151 | 152 | module.exports = { 153 | do_grafana: do_grafana 154 | }; 155 | 156 | // vim: set softtabstop=4 shiftwidth=4: 157 | -------------------------------------------------------------------------------- /lib/post-setup/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * 'sdcadm post-setup ...' CLI commands. 13 | */ 14 | 15 | var cmdln = require('cmdln'), 16 | Cmdln = cmdln.Cmdln; 17 | var util = require('util'); 18 | 19 | 20 | 21 | 22 | // --- PostSetup CLI class 23 | 24 | function PostSetupCLI(top) { 25 | this.top = top; 26 | Cmdln.call(this, { 27 | name: 'sdcadm post-setup', 28 | desc: 'Common post-setup procedures.\n' + 29 | '\n' + 30 | 'The default setup of a Triton Data Center headnode is somewhat\n' + 31 | 'minimal. "Everything up to adminui." Practical usage of\n' + 32 | 'SDC -- whether for production, development or testing --\n' + 33 | 'involves a number of common post-setup steps. This command\n' + 34 | 'attempts to capture many of those for convenience and\n' + 35 | 'consistency.\n', 36 | helpOpts: { 37 | minHelpCol: 27 38 | }, 39 | helpSubcmds: [ 40 | 'help', 41 | { group: 'General Setup', unmatched: true }, 42 | { group: 'Manta Setup' }, 43 | 'manta', 44 | { group: 'Development/Testing-only Setup' }, 45 | 'dev-headnode-prov', 46 | 'dev-sample-data' 47 | ] 48 | }); 49 | } 50 | util.inherits(PostSetupCLI, Cmdln); 51 | 52 | PostSetupCLI.prototype.init = function init(_opts, _args, _cb) { 53 | this.sdcadm = this.top.sdcadm; 54 | this.progress = this.top.progress; // Deprecated. Use `ui`. 55 | this.ui = this.top.ui; 56 | this.log = this.top.log; 57 | 58 | Cmdln.prototype.init.apply(this, arguments); 59 | }; 60 | 61 | 62 | PostSetupCLI.prototype.do_cloudapi = require('./cloudapi').do_cloudapi; 63 | PostSetupCLI.prototype.do_common_external_nics = 64 | require('./common-external-nics').do_common_external_nics; 65 | PostSetupCLI.prototype.do_underlay_nics = 66 | require('./underlay-nics').do_underlay_nics; 67 | PostSetupCLI.prototype.do_ha_binder = require('./ha-binder').do_ha_binder; 68 | PostSetupCLI.prototype.do_ha_binder.hiddenAliases = ['zookeeper']; 69 | PostSetupCLI.prototype.do_ha_manatee = require('./ha-manatee').do_ha_manatee; 70 | PostSetupCLI.prototype.do_fabrics = require('./fabrics').do_fabrics; 71 | 72 | PostSetupCLI.prototype.do_dev_headnode_prov = 73 | require('./dev-headnode-prov').do_dev_headnode_prov; 74 | PostSetupCLI.prototype.do_dev_sample_data = 75 | require('./dev-sample-data').do_dev_sample_data; 76 | 77 | PostSetupCLI.prototype.do_docker = require('./docker').do_docker; 78 | PostSetupCLI.prototype.do_cmon = require('./cmon').do_cmon; 79 | PostSetupCLI.prototype.do_cns = require('./cns').do_cns; 80 | PostSetupCLI.prototype.do_volapi = require('./volapi').do_volapi; 81 | PostSetupCLI.prototype.do_logarchiver = require('./logarchiver').do_logarchiver; 82 | PostSetupCLI.prototype.do_kbmapi = require('./kbmapi').do_kbmapi; 83 | PostSetupCLI.prototype.do_prometheus = require('./prometheus').do_prometheus; 84 | PostSetupCLI.prototype.do_grafana = require('./grafana').do_grafana; 85 | PostSetupCLI.prototype.do_firewall_logger_agent = 86 | require('./firewall-logger-agent').do_firewall_logger_agent; 87 | 88 | PostSetupCLI.prototype.do_manta = require('./do_manta'); 89 | 90 | // --- exports 91 | 92 | module.exports = { 93 | PostSetupCLI: PostSetupCLI 94 | }; 95 | -------------------------------------------------------------------------------- /lib/post-setup/kbmapi.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2022 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * The 'sdcadm post-setup kbmapi' CLI subcommand. 14 | */ 15 | 16 | var errors = require('../errors'); 17 | var AddServiceProc = require('../procedures/add-service').AddServiceProcedure; 18 | var runProcs = require('../procedures').runProcs; 19 | 20 | function do_kbmapi(subcmd, opts, args, cb) { 21 | var self = this; 22 | if (opts.help) { 23 | this.do_help('help', {}, [subcmd], cb); 24 | return; 25 | } else if (args.length > 0) { 26 | cb(new errors.UsageError('too many args: ' + args)); 27 | return; 28 | } 29 | 30 | 31 | 32 | const svcName = 'kbmapi'; 33 | const procOpts = { 34 | svcName: svcName, 35 | packageName: 'sdc_1024', 36 | delegatedDataset: false, 37 | networks: [ 38 | {name: 'admin'} 39 | ], 40 | firewallEnabled: false 41 | }; 42 | if (opts.image) { 43 | procOpts.image = opts.image; 44 | } 45 | 46 | if (opts.channel) { 47 | procOpts.channel = opts.channel; 48 | } 49 | 50 | const proc = new AddServiceProc(procOpts); 51 | 52 | runProcs({ 53 | log: self.log, 54 | procs: [proc], 55 | sdcadm: self.sdcadm, 56 | ui: self.ui, 57 | dryRun: opts.dry_run, 58 | skipConfirm: opts.yes 59 | }, cb); 60 | } 61 | 62 | do_kbmapi.options = [ 63 | { 64 | names: ['help', 'h'], 65 | type: 'bool', 66 | help: 'Show this help.' 67 | }, 68 | { 69 | names: ['yes', 'y'], 70 | type: 'bool', 71 | help: 'Answer yes to all confirmations.' 72 | }, 73 | { 74 | names: ['dry-run', 'n'], 75 | type: 'bool', 76 | help: 'Do a dry-run.' 77 | }, 78 | { 79 | group: 'Image selection (by default latest image on default ' + 80 | 'channel)' 81 | }, 82 | { 83 | names: ['image', 'i'], 84 | type: 'string', 85 | help: 'Specifies which image to use for the first instance. ' + 86 | 'Use "latest" (the default) for the latest available on ' + 87 | 'updates.tritondatacenter.com, "current" for the latest image ' + 88 | 'already in the datacenter (if any), or an image UUID or version.' 89 | }, 90 | { 91 | names: ['channel', 'C'], 92 | type: 'string', 93 | help: 'The updates.tritondatacenter.com channel from which to fetch ' + 94 | 'the image. See `sdcadm channel get` for the default channel.' 95 | } 96 | 97 | ]; 98 | 99 | do_kbmapi.help = [ 100 | 'Setup the Key Backup and Management API (KBMAPI) service', 101 | 'and create the first instance.', 102 | '', 103 | 'Usage:', 104 | ' {{name}} kbmapi', 105 | '', 106 | '{{options}}', 107 | 'The "kbmapi" service manages the pivtokens on Triton compute nodes' + 108 | ' containing encrypted zpools.' 109 | ].join('\n'); 110 | 111 | do_kbmapi.logToFile = true; 112 | 113 | // --- exports 114 | 115 | module.exports = { 116 | do_kbmapi: do_kbmapi 117 | }; 118 | 119 | // vim: set softtabstop=4 shiftwidth=4: 120 | -------------------------------------------------------------------------------- /lib/post-setup/logarchiver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2022 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * The 'sdcadm post-setup logarchiver' CLI subcommand. 14 | */ 15 | 16 | 17 | var errors = require('../errors'); 18 | var AddServiceProc = require('../procedures/add-service').AddServiceProcedure; 19 | var runProcs = require('../procedures').runProcs; 20 | 21 | function do_logarchiver(subcmd, opts, args, cb) { 22 | var self = this; 23 | if (opts.help) { 24 | this.do_help('help', {}, [subcmd], cb); 25 | return; 26 | } else if (args.length > 0) { 27 | cb(new errors.UsageError('too many args: ' + args)); 28 | return; 29 | } 30 | 31 | const svcName = 'logarchiver'; 32 | const procOpts = { 33 | svcName: svcName, 34 | packageName: 'sdc_1024', 35 | delegatedDataset: false, 36 | networks: [ 37 | {name: 'admin'} 38 | ], 39 | firewallEnabled: false 40 | }; 41 | if (opts.image) { 42 | procOpts.image = opts.image; 43 | } 44 | 45 | if (opts.channel) { 46 | procOpts.channel = opts.channel; 47 | } 48 | 49 | if (opts.server) { 50 | procOpts.server = opts.server; 51 | } 52 | 53 | const proc = new AddServiceProc(procOpts); 54 | 55 | runProcs({ 56 | log: self.log, 57 | procs: [proc], 58 | sdcadm: self.sdcadm, 59 | ui: self.ui, 60 | dryRun: opts.dry_run, 61 | skipConfirm: opts.yes 62 | }, cb); 63 | } 64 | 65 | do_logarchiver.options = [ 66 | { 67 | names: ['help', 'h'], 68 | type: 'bool', 69 | help: 'Show this help.' 70 | }, 71 | { 72 | names: ['yes', 'y'], 73 | type: 'bool', 74 | help: 'Answer yes to all confirmations.' 75 | }, 76 | { 77 | names: ['dry-run', 'n'], 78 | type: 'bool', 79 | help: 'Do a dry-run.' 80 | }, 81 | { 82 | names: ['server', 's'], 83 | type: 'string', 84 | help: 'Either hostname or uuid of the server on which to create' + 85 | ' the instance. (By default the headnode will be used.)', 86 | helpArg: 'SERVER' 87 | }, 88 | { 89 | group: 'Image selection (by default latest image on default ' + 90 | 'channel)' 91 | }, 92 | { 93 | names: ['image', 'i'], 94 | type: 'string', 95 | help: 'Specifies which image to use for the first instance. ' + 96 | 'Use "latest" (the default) for the latest available on ' + 97 | 'updates.tritondatacenter.com, "current" for the latest image ' + 98 | 'already in the datacenter (if any), or an image UUID or version.' 99 | }, 100 | { 101 | names: ['channel', 'C'], 102 | type: 'string', 103 | help: 'The updates.tritondatacenter.com channel from which to fetch ' + 104 | 'the image. See `sdcadm channel get` for the default channel.' 105 | } 106 | 107 | ]; 108 | 109 | do_logarchiver.help = [ 110 | 'Create the "logarchiver" service and a first instance.', 111 | '', 112 | 'Usage:', 113 | ' {{name}} logarchiver', 114 | '', 115 | '{{options}}', 116 | 'The "logarchiver" service uploads specific Triton log files to a' + 117 | ' configured Manta object store.' 118 | ].join('\n'); 119 | 120 | do_logarchiver.logToFile = true; 121 | 122 | // --- exports 123 | 124 | module.exports = { 125 | do_logarchiver: do_logarchiver 126 | }; 127 | 128 | // vim: set softtabstop=4 shiftwidth=4: 129 | -------------------------------------------------------------------------------- /lib/procedures/procedure.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * A "Procedure" API for managing execution of some of the larger sdcadm tasks. 13 | * For smaller tasks encapsulated as a single function, see 14 | * "lib/steps/README.md". 15 | * 16 | * `sdcadm` has a number of "Procedure" subclasses for doing some larger task, 17 | * e.g. downloading images for an update, updating one of the simple stateless 18 | * core service instances, updating manatee. The expected usage is: 19 | * 20 | * 1. create an array of one or more procedure objects: 21 | * 22 | * var procs = [new DownloadImages(...), new UpdateMoray(...)]; 23 | * 24 | * 2. prepare the procedures, during which they gather necessary information 25 | * from the system, error out if it cannot be accomplished, and callback 26 | * with whether they have work to do: 27 | * 28 | * vasync.forEachParallel({ 29 | * inputs: procs, 30 | * func: function prepareProc(proc, nextProc) { 31 | * proc.prepare({ 32 | * log: log, 33 | * ui: ui, 34 | * sdcadm: sdcadm 35 | * }, function onPrepare(err, nothingToDo) { 36 | * // ... 37 | * }); 38 | * } 39 | * }, function (err) { 40 | * // ... 41 | * }); 42 | * 43 | * 3. Use `.summarize(...)` for each procedure to show what will be done, and 44 | * get confirmation from the operator before proceeding. 45 | * 46 | * 4. Call `.execute(...)` on each procedure in series. 47 | * 48 | * See `runProcs()` in "lib/procedures/index.js" for a method to handle this 49 | * usage. 50 | */ 51 | 52 | var assert = require('assert-plus'); 53 | var VError = require('verror'); 54 | 55 | 56 | // Create a procedure. All configuration defining a procedure should be 57 | // passed into its constructor. 58 | function Procedure(_opts) {} 59 | 60 | // Prepare the procedure. This involves: 61 | // 62 | // - gathering necessary system data to determine the exact steps to perform, 63 | // - calling back with an error if the procedure is not viable, 64 | // (e.g. if a requested server is not running, or a required service is down), 65 | // - calling back with a boolean if the procedure has nothing to do (e.g. if 66 | // an image to download is already in the DC's IMGAPI, or a service to add 67 | // is already in SAPI) 68 | // 69 | // This will be called before `.summarize`, so this provides an 70 | // opportunity for the procedure to gather info necessary for a useful 71 | // summary string. 72 | // 73 | // @param {Object} opts.log - Bunyan logger. 74 | // @param {Object} opts.sdcadm 75 | // @param {Object} opts.ui - see "lib/cli/ui.js". 76 | // @param {Function} cb - `function (err, nothingToDo)` 77 | // `err` is null or is an Error with the reason(s) the procedure 78 | // (as configured) cannot be performed. If `err` is null, and the procedure 79 | // has nothing to do (the work has already been done), then `nothingToDo` 80 | // is `true`. If so, then the caller need not call its `execute`. 81 | Procedure.prototype.prepare = function prepare(opts, cb) { 82 | assert.object(opts.log, 'opts.log'); 83 | assert.object(opts.sdcadm, 'opts.sdcadm'); 84 | assert.object(opts.ui, 'opts.ui'); 85 | 86 | // By default a procedure is viable and should be executed. 87 | cb(null, false); 88 | }; 89 | 90 | // @returns {String} A bullet point summary of work that will be done. 91 | Procedure.prototype.summarize = function summarize() {}; 92 | 93 | // Execute the procedure. 94 | // 95 | // TODO: Spec the required `opts` for this. Currently there is a large 96 | // mishmash used in SdcAdm.execUpdatePlan. This should be reduced. Odd 97 | // params can come in via Procedure constructors. 98 | Procedure.prototype.execute = function execute(opts, cb) { 99 | assert.object(opts.log, 'opts.log'); 100 | assert.object(opts.sdcadm, 'opts.sdcadm'); 101 | assert.func(opts.progress, 'opts.progress'); // Deprecated. Use `ui`. 102 | assert.object(opts.ui, 'opts.ui'); 103 | 104 | cb(new VError({name: 'NotImplementedError'}, 105 | this.constructor.name + '.execute() is not implemented')); 106 | }; 107 | 108 | 109 | // ---- exports 110 | 111 | module.exports = { 112 | Procedure: Procedure 113 | }; 114 | 115 | // vim: set softtabstop=4 shiftwidth=4: 116 | -------------------------------------------------------------------------------- /lib/procedures/set-mantav2-migration-metadata.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2020 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * This is a Procedure to set metadata on the "manta" SAPI application as 13 | * appropriate for beginning migration of a Mantav1 to a Mantav2. 14 | * 15 | * Basically this involves setting `MANTAV=2` plus a number of other boolean 16 | * metadata, such as `SNAPLINK_CLEANUP_REQUIRED=true`, as a signal to 17 | * subsequent migration steps. 18 | */ 19 | 20 | 'use strict'; 21 | 22 | const assert = require('assert-plus'); 23 | const util = require('util'); 24 | const vasync = require('vasync'); 25 | const VError = require('verror'); 26 | 27 | const errors = require('../errors'); 28 | const Procedure = require('./procedure').Procedure; 29 | 30 | 31 | 32 | // ---- internal support functions 33 | 34 | 35 | // ---- the procedure 36 | 37 | function SetMantav2MigrationMetadataProcedure() { 38 | } 39 | util.inherits(SetMantav2MigrationMetadataProcedure, Procedure); 40 | 41 | SetMantav2MigrationMetadataProcedure.prototype.summarize = 42 | function summarize() { 43 | return 'Set MANTAV=2 and other metadata on the "manta" SAPI application\n' + 44 | ' to mark the start of migration to mantav2'; 45 | }; 46 | 47 | SetMantav2MigrationMetadataProcedure.prototype.execute = 48 | function execute(opts, cb) { 49 | assert.object(opts, 'opts'); 50 | assert.func(cb, 'cb'); 51 | assert.object(opts.sdcadm, 'opts.sdcadm'); 52 | assert.object(opts.ui, 'opts.ui'); 53 | assert.object(opts.log, 'opts.log'); 54 | 55 | const sdcadm = opts.sdcadm; 56 | const ui = opts.ui; 57 | 58 | vasync.pipeline({ 59 | arg: {}, 60 | funcs: [ 61 | function getApp(ctx, next) { 62 | sdcadm.sapi.listApplications({ 63 | name: 'manta', 64 | include_master: true 65 | }, function onApps(err, apps) { 66 | if (err) { 67 | next(new errors.SDCClientError(err, 'sapi')); 68 | } else if (apps.length > 1) { 69 | next(new VError('multiple "manta" apps found!')); 70 | } else if (apps.length === 0) { 71 | next(new VError('no "manta" application found')); 72 | } else { 73 | ctx.app = apps[0]; 74 | next(); 75 | } 76 | }); 77 | }, 78 | 79 | function setMetadata(ctx, next) { 80 | let update = { 81 | // Current SAPI version implicitly assumes `include_master` 82 | // for UpdateApplication (and others). It doesn't actually 83 | // look at an `include_master` param. 84 | // include_master: true, 85 | metadata: { 86 | MANTAV: 2, 87 | 88 | // Boolean metadata to mark the need for subsequent 89 | // migration steps. 90 | SNAPLINK_CLEANUP_REQUIRED: true, 91 | MANTA_DELETE_LOG_CLEANUP_REQUIRED: true, 92 | REPORTS_CLEANUP_REQUIRED: true, 93 | ARCHIVED_JOBS_CLEANUP_REQUIRED: true, 94 | MANTAV1_MPU_UPLOADS_CLEANUP_REQUIRED: true 95 | } 96 | }; 97 | 98 | sdcadm.sapi.updateApplication(ctx.app.uuid, update, 99 | errors.sdcClientErrWrap(next, 'sapi')); 100 | }, 101 | 102 | function letTheCallerKnow(_, next) { 103 | ui.info('Set mantav2 migration metadata (MANTAV=2, ' 104 | + 'SNAPLINK_CLEANUP_REQUIRED=true, etc.)'); 105 | next(); 106 | } 107 | ] 108 | }, cb); 109 | }; 110 | 111 | 112 | // --- exports 113 | 114 | module.exports = { 115 | SetMantav2MigrationMetadataProcedure: SetMantav2MigrationMetadataProcedure 116 | }; 117 | -------------------------------------------------------------------------------- /lib/procedures/update-sapi-v2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | var assert = require('assert-plus'); 11 | var sprintf = require('extsprintf').sprintf; 12 | var util = require('util'); 13 | var vasync = require('vasync'); 14 | 15 | var common = require('../common'); 16 | var errors = require('../errors'); 17 | var Procedure = require('./procedure').Procedure; 18 | var s = require('./shared'); 19 | 20 | function UpdateSapiV2(options) { 21 | assert.arrayOfObject(options.changes, 'options.changes'); 22 | this.changes = options.changes; 23 | } 24 | util.inherits(UpdateSapiV2, Procedure); 25 | 26 | UpdateSapiV2.prototype.summarize = function sapiv2Summarize() { 27 | var out = []; 28 | this.changes.forEach(function summarizeChange(ch) { 29 | if (ch.type === 'update-instance') { 30 | out.push(sprintf('update instance "%s" (%s)\n' + 31 | 'of service "%s" to image %s\n', ch.inst.instance, 32 | ch.inst.alias, ch.service.name, ch.image.uuid), 33 | common.indent(sprintf('(%s@%s)', 34 | ch.image.name, ch.image.version))); 35 | } else { 36 | var word = (ch.type === 'rollback-service') ? 37 | 'rollback' : 'update'; 38 | var img = ch.image; 39 | var msg = sprintf('%s "%s" service to image %s\n', 40 | word, ch.service.name, img.uuid) + 41 | common.indent(sprintf('(%s@%s)', img.name, img.version)); 42 | 43 | if (ch.insts) { 44 | msg += ':\n'; 45 | msg += ch.insts.map(function (inst) { 46 | return common.indent(sprintf( 47 | 'instance "%s" (%s) on server %s', 48 | inst.zonename, inst.alias, inst.server)); 49 | }).join('\n'); 50 | } else if (ch.inst) { 51 | msg += ':\n'; 52 | msg += common.indent(sprintf( 53 | 'instance "%s" (%s) on server %s', 54 | ch.inst.zonename, ch.inst.alias, ch.inst.server)); 55 | } 56 | out.push(msg); 57 | } 58 | }); 59 | 60 | return out.join('\n'); 61 | }; 62 | 63 | 64 | UpdateSapiV2.prototype.execute = function sapiv2Execute(opts, cb) { 65 | assert.object(opts, 'opts'); 66 | assert.object(opts.sdcadm, 'opts.sdcadm'); 67 | assert.object(opts.plan, 'opts.plan'); 68 | assert.object(opts.log, 'opts.log'); 69 | assert.func(opts.progress, 'opts.progress'); 70 | assert.string(opts.wrkDir, 'opts.wrkDir'); 71 | assert.func(cb, 'cb'); 72 | var self = this; 73 | var progress = opts.progress; 74 | var rollback = opts.plan.rollback || false; 75 | var sdcadm = opts.sdcadm; 76 | 77 | 78 | function updateSapi(change, nextSvc) { 79 | 80 | const SAPI_URL = 'http://' + change.service.metadata.SERVICE_DOMAIN; 81 | 82 | var arg = { 83 | change: change, 84 | opts: opts, 85 | userScript: false, 86 | tmpUUID: null 87 | }; 88 | 89 | if (!change.insts) { 90 | change.insts = change.inst ? [change.inst] : []; 91 | } 92 | 93 | if (opts.plan.changes.length > 1) { 94 | progress(''); 95 | progress('--- Updating %s ...', change.service.name); 96 | } 97 | 98 | var funcs = []; 99 | 100 | if (rollback) { 101 | funcs.push(s.getOldUserScript); 102 | } else { 103 | funcs.push(s.getUserScript); 104 | funcs.push(s.writeOldUserScriptForRollback); 105 | } 106 | funcs.push(s.updateSvcUserScript); 107 | 108 | change.insts.forEach(function updateInstsVmUserScript(ins) { 109 | funcs.push(function updateVmUserScript(_, next) { 110 | s.updateVmUserScriptRemote({ 111 | service: change.service, 112 | progress: progress, 113 | zonename: ins.zonename, 114 | log: opts.log, 115 | server: ins.server, 116 | userScript: arg.userScript 117 | }, next); 118 | }); 119 | }); 120 | 121 | funcs.push(s.updateSapiSvc); 122 | // Workaround SAPI-199 and TOOLS-638 123 | funcs.push(function updateServiceSapiURL(_, next) { 124 | progress('Updating \'sapi-url\' in SAPI'); 125 | sdcadm.sapi.updateService(change.service.uuid, { 126 | metadata: { 127 | 'sapi-url': SAPI_URL 128 | } 129 | }, errors.sdcClientErrWrap(next, 'sapi')); 130 | }); 131 | 132 | change.insts.forEach(function (ins) { 133 | funcs = funcs.concat( 134 | function imgadmInstallForInstance(_, next) { 135 | return s.imgadmInstallRemote({ 136 | progress: progress, 137 | img: change.image, 138 | log: opts.log, 139 | server: ins.server 140 | }, next); 141 | }, 142 | function reprovisionInstance(_, next) { 143 | s.reprovisionRemote({ 144 | server: ins.server, 145 | img: change.image, 146 | zonename: ins.zonename, 147 | progress: progress, 148 | log: opts.log, 149 | sdcadm: opts.sdcadm 150 | }, next); 151 | }, 152 | function waitForInstanceToBeUp(_, next) { 153 | s.waitForInstToBeUp({ 154 | opts: { 155 | progress: progress, 156 | sdcadm: opts.sdcadm, 157 | log: opts.log 158 | }, 159 | change: { 160 | inst: ins 161 | } 162 | }, next); 163 | } 164 | ); 165 | }); 166 | 167 | vasync.pipeline({funcs: funcs, arg: arg}, nextSvc); 168 | 169 | } 170 | 171 | vasync.forEachPipeline({ 172 | inputs: self.changes, 173 | func: updateSapi 174 | }, cb); 175 | }; 176 | 177 | // --- exports 178 | 179 | module.exports = { 180 | UpdateSapiV2: UpdateSapiV2 181 | }; 182 | // vim: set softtabstop=4 shiftwidth=4: 183 | -------------------------------------------------------------------------------- /lib/steps/README.md: -------------------------------------------------------------------------------- 1 | A "step" is a JS function with the signature: 2 | 3 | function (arg, next) 4 | 5 | It is meant to encapsulate a useful chunk of work (a step) done for some 6 | sdcadm process, e.g. a small thing like "run imgadm import of the given UUID 7 | locally", or a larger thing like "wait for the given instance (a VM) to come 8 | online". Typically the "sdcadm process" here is a part of `sdcadm up ...` or 9 | `sdcadm post-setup ...`. 10 | 11 | Note that sdcadm already has the concept of "procedures" for encapsulating 12 | chunks of work for "sdcadm up ...". However those are heavier weight. Each 13 | "procedure" is an object with `.summarize()` and `.execute()` methods (see 14 | "lib/procedures/procedure.js"). Procedures' `execute` will often be made 15 | up of a number of steps. 16 | 17 | For a good inspiration see 18 | . This is the "procedure" for updating a set of 19 | the "stateless" SDC services like vmapi, cnapi, papi. That code is 20 | fairly tight because it just orders a list of steps to run for each 21 | service, where each step is defined elsewhere. 22 | 23 | 24 | # Goals for "steps" 25 | 26 | - Separate smaller files for easier maintenance. 27 | - Easier discovery so there is more re-use of these steps. 28 | - Some attempt at standardization of the steps' args and handling. 29 | 30 | The hope is that this leads to tighter and more self-explanatory 31 | `vasync.pipeline`s in other sdcadm code. 32 | 33 | 34 | # Code organization 35 | 36 | You can have one step (a function) per .js file... or if there are a few 37 | related steps, then group them in a common js file. Let's have each 38 | exported step *re-exported* from "lib/steps/index.js", then typical 39 | usage can be: 40 | 41 | var steps = require('./steps'); 42 | 43 | // ... 44 | vasync.pipeline({arg: contextArg, funcs: [ 45 | steps.widget.doACommonThing, 46 | steps.widget.doAnotherCommonThing, 47 | function aStepSpecificToHere(arg, next) { 48 | // ... 49 | }, 50 | steps.wuzzle.finishWithThisCommonThing 51 | ]}, function (err) { 52 | // ... 53 | }); 54 | 55 | 56 | # TODO 57 | 58 | - At the time of writing "lib/procedures/shared.js" has a lot of 59 | functions with the same idea. I propose moving those to "lib/steps/\*.js" 60 | over time. 61 | - I expect that we'll want curried versions of some of these steps. E.g.: 62 | 63 | vasync.pipeline({arg: contextArg, funcs: [ 64 | // ... 65 | steps.waitForInstToBeUp('imgapiInst'), 66 | // ... 67 | 68 | where `'imgapiInst'` is the name of variable on the context `arg` with the 69 | instance details. Feel that out. 70 | -------------------------------------------------------------------------------- /lib/steps/dnsdomain.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * Steps for setting the proper "dns_domain" value for SDC application. 13 | */ 14 | 15 | var assert = require('assert-plus'); 16 | 17 | var errors = require('../errors'); 18 | 19 | function ensureDnsDomainSdcAppParam(arg, cb) { 20 | assert.object(arg, 'arg'); 21 | assert.func(arg.progress, 'arg.progress'); 22 | assert.object(arg.sdcadm, 'arg.sdcadm'); 23 | assert.object(arg.sdcadm.sdcApp, 'arg.sdcadm.sdcApp'); 24 | assert.func(cb, 'cb'); 25 | 26 | var sdcadm = arg.sdcadm; 27 | var app = arg.sdcadm.sdcApp; 28 | 29 | if (app.params.dns_domain) { 30 | cb(); 31 | return; 32 | } 33 | 34 | arg.progress('Setting "params.dns_domain" on Sdc Application'); 35 | 36 | sdcadm.sapi.updateApplication(app.uuid, { 37 | params: { 38 | dns_domain: app.metadata.dns_domain 39 | } 40 | }, function updateAppCb(sapiErr) { 41 | if (sapiErr) { 42 | cb(new errors.SDCClientError(sapiErr, 'sapi')); 43 | return; 44 | } 45 | cb(); 46 | }); 47 | } 48 | 49 | // --- exports 50 | 51 | module.exports = { 52 | ensureDnsDomainSdcAppParam: ensureDnsDomainSdcAppParam 53 | }; 54 | 55 | // vim: set softtabstop=4 shiftwidth=4: 56 | -------------------------------------------------------------------------------- /lib/steps/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * The collection of "step" functions. Re-usable chunks of sdcadm code. 13 | * See the "README.md". 14 | */ 15 | 'use strict'; 16 | 17 | // --- exports 18 | 19 | module.exports = { 20 | binder: require('./binder'), 21 | dnsdomain: require('./dnsdomain'), 22 | images: require('./images'), 23 | noRabbit: require('./noRabbit'), 24 | sapi: require('./sapi'), 25 | servers: require('./servers'), 26 | updateVmSize: require('./updateVmSize'), 27 | usbkey: require('./usbkey'), 28 | zookeeper: require('./zookeeper') 29 | }; 30 | 31 | // vim: set softtabstop=4 shiftwidth=4: 32 | -------------------------------------------------------------------------------- /lib/steps/usbkey.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018 Joyent, Inc. 9 | */ 10 | 'use strict'; 11 | 12 | const fs = require('fs'); 13 | const path = require('path'); 14 | 15 | var assert = require('assert-plus'); 16 | 17 | 18 | // keep a finite number cn_tools backups on the usb key 19 | function removeOldCNToolsTarballs(arg, next) { 20 | assert.optionalFunc(arg.progress, 'arg.progress'); 21 | assert.func(next, 'next'); 22 | 23 | const progress = arg.progress || console.log; 24 | const backupPath = '/usbkey/extra/joysetup/'; 25 | let tarballs = fs.readdirSync(backupPath).filter( 26 | function isCNTools(p) { 27 | return (p.startsWith('cn_tools.') && 28 | p.endsWith('tar.gz') && 29 | p !== 'cn_tools.tar.gz'); 30 | }); 31 | tarballs.sort(); 32 | tarballs.reverse(); 33 | const toDelete = tarballs.slice(4); 34 | if (toDelete.length) { 35 | progress('Removing old cn backups: ' + toDelete.join(', ')); 36 | toDelete.forEach(function rmBall(fname) { 37 | fs.unlinkSync(path.join(backupPath, fname)); 38 | }); 39 | } 40 | next(); 41 | } 42 | 43 | // keep only a finite number of agentsshar files 44 | function removeOldAgentsShars(arg, next) { 45 | assert.optionalFunc(arg.progress, 'arg.progress'); 46 | assert.func(next, 'next'); 47 | 48 | const progress = arg.progress || console.log; 49 | const agentsDir = '/usbkey/extra/agents'; 50 | const latestLinkName = '/usbkey/extra/agents/latest'; 51 | 52 | let latest; 53 | if (fs.existsSync('/usbkey/extra/agents/latest')) { 54 | latest = path.resolve(agentsDir, 55 | fs.readlinkSync(latestLinkName)); 56 | } 57 | 58 | let shars = fs.readdirSync('/usbkey/extra/agents/').filter( 59 | function isShar(p) { 60 | return (p.endsWith('.sh') && 61 | // Prefix was changed from agent- to agents- in TOOLS-1958 62 | (p.startsWith('agents-') || p.startsWith('agent-')) && 63 | path.resolve(agentsDir, p) !== latest); 64 | }); 65 | 66 | // With the possible exception of the first run, there should only be a 67 | // handful of old agent shars to consider. The full agent install already 68 | // takes minutes. 69 | const sortedShars = shars.map(function statShar(fname) { 70 | return {fname: fname, 71 | mtime: fs.statSync(path.resolve(agentsDir, fname)).mtime}; 72 | }).sort(function cmp(a, b) { 73 | return a.mtime - b.mtime; 74 | }).map(function (pair) { 75 | return pair.fname; 76 | }); 77 | sortedShars.reverse(); 78 | const toDelete = sortedShars.slice(3); 79 | if (toDelete.length) { 80 | progress('Removing old agentshars: ' + toDelete.join(', ')); 81 | toDelete.forEach(function rmShar(fname) { 82 | fs.unlinkSync(path.join(agentsDir, fname)); 83 | }); 84 | } 85 | next(); 86 | } 87 | 88 | module.exports = { 89 | removeOldCNToolsTarballs: removeOldCNToolsTarballs, 90 | removeOldAgentsShars: removeOldAgentsShars 91 | }; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdcadm", 3 | "description": "Administer a Triton Data Center", 4 | "version": "1.39.0", 5 | "author": "MNX Cloud (mnx.io)", 6 | "private": true, 7 | "dependencies": { 8 | "assert-plus": "^1.0.0", 9 | "async": "0.2.9", 10 | "backoff": "2.5.0", 11 | "bunyan": "1.8.12", 12 | "cmdln": "4.4.0", 13 | "cueball": "2.4.0", 14 | "extsprintf": "^1.3.0", 15 | "joyent-schemas": "git+https://github.com/TritonDataCenter/schemas.git#3ca3a09", 16 | "jsprim": "1.2.2", 17 | "kthxbai": "~0.4.0", 18 | "mkdirp": "0.3.5", 19 | "node-uuid": "1.4.1", 20 | "once": "1.3.0", 21 | "progbar": "1.2.0", 22 | "qlocker": "^1.0.1", 23 | "read": "1.0.5", 24 | "restify-clients": "1.4.0", 25 | "sdc-clients": "13.0.3", 26 | "semver": "5.4.1", 27 | "strsplit": "1.0.0", 28 | "tabula": "1.9.0", 29 | "tape": "4.10.1", 30 | "triton-netconfig": "1.4.0", 31 | "ufds": "1.7.1", 32 | "urclient": "1.2.0", 33 | "vasync": "2.2.0", 34 | "verror": "1.10.0", 35 | "wf-client": "0.3.0" 36 | }, 37 | "devDependencies": { 38 | "marked-man": "0.1.4", 39 | "eslint": "4.19.1", 40 | "eslint-plugin-joyent": "~2.0.0", 41 | "mock-fs": "4.5.x", 42 | "tap": "^12.5.2", 43 | "zkstream": "0.11.2", 44 | "mockery": "^2.1.0" 45 | }, 46 | "engines": { 47 | "node": ">=4.x" 48 | }, 49 | "license": "MPL-2.0" 50 | } 51 | -------------------------------------------------------------------------------- /smf/manifests/sdcadm-setup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /smf/method/sdcadm-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2015, Joyent, Inc. 10 | # Copyright 2024 MNX Cloud, Inc. 11 | # 12 | 13 | # 14 | # "sdcadm-setup" service for restoring sdcadm GZ settings on server reboot. 15 | # 16 | 17 | set -o xtrace 18 | 19 | . /lib/svc/share/smf_include.sh 20 | 21 | function setup_logadm { 22 | # Even though our '-b cmd' creates this file, logadm rotation will not rotate 23 | # if the 'logs' dir and 'sdcadm.log' file don't exist. 24 | mkdir -p /var/log/sdcadm/logs 25 | touch /var/log/sdcadm/sdcadm.log 26 | 27 | logadm -w sdcadm_logs \ 28 | -b '/opt/smartdc/sdcadm/tools/rotate-logs.sh -i /var/log/sdcadm/logs/ /var/log/sdcadm/sdcadm.log' \ 29 | -t '/var/log/sdcadm/sdcadm_$nodename_%FT%H:%M:%S.log' \ 30 | -C 168 -S 1g -p 1h \ 31 | /var/log/sdcadm/sdcadm.log 32 | 33 | # Move the smf_logs entry to run last (after the entries we just added) so 34 | # that the default '-C' (from 35 | # https://github.com/TritonDataCenter/smartos-live/blob/master/overlay/generic/etc/logadm.conf) 36 | # doesn't defeat our attempts to rotate out of "/var/svc/log". 37 | /usr/sbin/logadm -r smf_logs 38 | /usr/sbin/logadm -w smf_logs -C 8 -c -s 1m '/var/svc/log/*.log' 39 | } 40 | 41 | 42 | setup_logadm 43 | 44 | # Create symlink to sdcadm.completion from /etc/bash/bash_completion.d/ 45 | 46 | if [[ -h /etc/bash/bash_completion.d/sdcadm ]]; then 47 | rm /etc/bash/bash_completion.d/sdcadm 48 | fi 49 | 50 | cd /etc/bash/bash_completion.d 51 | ln -s /opt/smartdc/sdcadm/etc/sdcadm.completion sdcadm 52 | 53 | exit $SMF_EXIT_OK 54 | -------------------------------------------------------------------------------- /test/available.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018, Joyent, Inc. 9 | */ 10 | 11 | 12 | var test = require('tape').test; 13 | 14 | var exec = require('child_process').exec; 15 | 16 | var common = require('./common'); 17 | var shared = require('./shared'); 18 | 19 | var AVAIL_TITLES = ['SERVICE', 'IMAGE', 'VERSION']; 20 | 21 | function parseAvailOutput(t, output, expectedTitles) { 22 | var availDetails = common.parseTextOut(output); 23 | t.ok(availDetails.length > 0); 24 | 25 | if (availDetails.length > 1) { 26 | var titles = availDetails.shift(); 27 | t.deepEqual(titles, expectedTitles || AVAIL_TITLES, 28 | 'check column titles'); 29 | 30 | return availDetails; 31 | } else { 32 | var up2Date = availDetails.shift(); 33 | t.deepEqual(up2Date, ['Up-to-date.'], 'check column titles'); 34 | return availDetails; 35 | } 36 | } 37 | 38 | test('setup', function (t) { 39 | shared.prepare(t, {external_nics: true}); 40 | }); 41 | 42 | test('sdcadm available --help', function (t) { 43 | exec('sdcadm available --help', function (err, stdout, stderr) { 44 | t.ifError(err); 45 | 46 | t.notEqual(stdout.indexOf( 47 | 'sdcadm avail(able) [] []'), -1); 48 | t.equal(stderr, ''); 49 | 50 | t.end(); 51 | }); 52 | }); 53 | 54 | 55 | test('sdcadm avail', function (t) { 56 | exec('sdcadm avail', function (err, stdout, stderr) { 57 | t.ifError(err, 'Execution error'); 58 | t.equal(stderr, '', 'Empty stderr'); 59 | 60 | var availDetails = parseAvailOutput(t, stdout); 61 | var foundSvcs = []; 62 | availDetails.forEach(function (svc) { 63 | t.equal(svc.length, 3, 'Service version and image'); 64 | t.equal(foundSvcs.indexOf(svc[0]), -1, 'Duplicated service'); 65 | foundSvcs.push(svc[0]); 66 | }); 67 | t.end(); 68 | }); 69 | 70 | }); 71 | 72 | 73 | test('sdcadm available --all-images', function (t) { 74 | exec('sdcadm available --all-images', function (err, stdout, stderr) { 75 | t.ifError(err, 'Execution error'); 76 | t.equal(stderr, '', 'Empty stderr'); 77 | 78 | var availDetails = parseAvailOutput(t, stdout); 79 | availDetails.forEach(function (svc) { 80 | t.equal(svc.length, 3, 'Service version and image'); 81 | }); 82 | t.end(); 83 | }); 84 | 85 | }); 86 | 87 | 88 | test('sdcadm avail -a manta', function (t) { 89 | exec('sdcadm avail -a manta', function (err, stdout, stderr) { 90 | t.ifError(err, 'Execution error'); 91 | t.equal(stderr, '', 'Empty stderr'); 92 | var availDetails = parseAvailOutput(t, stdout); 93 | availDetails.forEach(function (svc) { 94 | t.equal(svc.length, 3, 'Service version and image'); 95 | }); 96 | t.end(); 97 | }); 98 | 99 | }); 100 | 101 | 102 | test('sdcadm avail unknown', function (t) { 103 | exec('sdcadm avail unknown', function (err, stdout, stderr) { 104 | t.ok(err, 'Unknown service error'); 105 | t.notEqual(stderr.indexOf( 106 | 'unknown SDC instance or service "unknown"'), -1); 107 | t.end(); 108 | }); 109 | 110 | }); 111 | 112 | 113 | test('sdcadm avail --json', function (t) { 114 | exec('sdcadm avail --json', function (err, stdout, stderr) { 115 | t.ifError(err, 'Execution error'); 116 | t.equal(stderr, '', 'Empty stderr'); 117 | 118 | var foundSvcs = []; 119 | var jsonDetails = common.parseJsonOut(stdout); 120 | jsonDetails.forEach(function (svc) { 121 | t.ok(svc.service, 'Service name'); 122 | t.ok(svc.image, 'Available service Image'); 123 | t.ok(svc.version, 'Available service version'); 124 | t.equal(foundSvcs.indexOf(svc.service), -1, 'Duplicated service'); 125 | foundSvcs.push(svc.service); 126 | }); 127 | t.end(); 128 | }); 129 | 130 | }); 131 | -------------------------------------------------------------------------------- /test/channel.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2016, Joyent, Inc. 9 | */ 10 | 11 | var util = require('util'); 12 | var test = require('tape').test; 13 | var exec = require('child_process').exec; 14 | 15 | var CURR_CHANNEL = null; 16 | 17 | test('setup', function (t) { 18 | exec('sdcadm channel get', function (err, stdout, stderr) { 19 | t.ifError(err); 20 | t.equal(stderr, ''); 21 | if (stdout) { 22 | CURR_CHANNEL = stdout.trim(); 23 | } 24 | t.end(); 25 | }); 26 | }); 27 | 28 | test('sdcadm channel --help', function (t) { 29 | exec('sdcadm channel --help', function (err, stdout, stderr) { 30 | t.ifError(err); 31 | 32 | t.notEqual(stdout.indexOf('sdcadm channel [OPTIONS] COMMAND', -1)); 33 | t.equal(stderr, ''); 34 | 35 | t.end(); 36 | }); 37 | }); 38 | 39 | 40 | test('sdcadm channel list', function (t) { 41 | exec('sdcadm channel list', function (err, stdout, stderr) { 42 | t.ifError(err); 43 | t.equal(stderr, ''); 44 | 45 | var expected = [ 46 | 'experimental', 47 | 'dev', 48 | 'staging', 49 | 'release', 50 | 'support' 51 | ]; 52 | 53 | expected.forEach(function (name) { 54 | t.ok(stdout.match(name), 'contains name: ' + name); 55 | }); 56 | 57 | var lines = stdout.split('\n'); 58 | var titles = lines[0].split(/\s+/); 59 | t.deepEqual(titles, ['NAME', 'DEFAULT', 'DESCRIPTION']); 60 | t.end(); 61 | }); 62 | }); 63 | 64 | 65 | test('sdcadm channel set', function (t) { 66 | exec('sdcadm channel set release', function (err, stdout, stderr) { 67 | t.ifError(err); 68 | t.equal(stdout.trim(), 69 | 'Update channel has been successfully set to: \'release\''); 70 | t.equal(stderr, ''); 71 | t.end(); 72 | }); 73 | }); 74 | 75 | 76 | test('sdcadm channel reset', function (t) { 77 | if (CURR_CHANNEL === null) { 78 | t.end(); 79 | return; 80 | } 81 | var cmd = util.format('sdcadm channel set %s', CURR_CHANNEL); 82 | exec(cmd, function (err, stdout, stderr) { 83 | t.ifError(err); 84 | 85 | t.equal(stdout.trim(), 'Update channel has been successfully set to: ' + 86 | '\'' + CURR_CHANNEL + '\''); 87 | t.equal(stderr, ''); 88 | t.end(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/check-config.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018, Joyent, Inc. 9 | */ 10 | 11 | 12 | var test = require('tape').test; 13 | var exec = require('child_process').exec; 14 | 15 | 16 | test('sdcadm check-config --help', function (t) { 17 | exec('sdcadm check-config --help', function (err, stdout, stderr) { 18 | t.ifError(err); 19 | 20 | t.ok(stdout.indexOf('sdcadm check-config []') !== -1); 21 | t.equal(stderr, ''); 22 | 23 | t.end(); 24 | }); 25 | }); 26 | 27 | 28 | test('sdcadm check-config', function (t) { 29 | exec('sdcadm check-config', function (err, stdout, stderr) { 30 | t.ifError(err); 31 | 32 | t.equal(stdout, 'All good!\n'); 33 | t.equal(stderr, ''); 34 | 35 | t.end(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018, Joyent, Inc. 9 | */ 10 | 11 | var exec = require('child_process').exec; 12 | var util = require('util'); 13 | 14 | var vasync = require('vasync'); 15 | 16 | var DEFAULT_VM_SERVICES = [ 17 | 'adminui', 'amon', 'amonredis', 'assets', 'binder', 'cnapi', 'dhcpd', 18 | 'fwapi', 'imgapi', 'mahi', 'manatee', 'moray', 'napi', 'papi', 'rabbitmq', 19 | 'redis', 'sapi', 'sdc', 'ufds', 'vmapi', 'workflow' 20 | ]; 21 | 22 | var ALL_VM_SERVICES = DEFAULT_VM_SERVICES.concat([ 23 | 'portolan', 'cloudapi', 'docker', 'cns' 24 | ]); 25 | 26 | var DEFAULT_AGENT_SERVICES = [ 27 | 'amon-agent', 'amon-relay', 'cainstsvc', 'firewaller', 28 | 'cn-agent', 'vm-agent', 'net-agent', 'smartlogin', 29 | 'hagfish-watcher' 30 | ]; 31 | 32 | var ALL_AGENT_SERVICES = DEFAULT_AGENT_SERVICES.concat(['dockerlogger']); 33 | 34 | var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; 35 | 36 | 37 | function deepCopy(obj) { 38 | return JSON.parse(JSON.stringify(obj)); // heh 39 | } 40 | 41 | 42 | function parseJsonOut(output) { 43 | try { 44 | return JSON.parse(output); 45 | } catch (_) { 46 | return null; // dodgy 47 | } 48 | } 49 | 50 | 51 | function parseTextOut(output) { 52 | return output.split('\n').filter(function (r) { 53 | return r !== ''; 54 | }).map(function (r) { 55 | return r.split(/\s+/); 56 | }); 57 | } 58 | 59 | 60 | function checkHelp(t, subcommand, match) { 61 | exec('sdcadm help ' + subcommand, function (err, stdout, stderr) { 62 | t.ifError(err); 63 | 64 | t.notEqual(stdout.indexOf(match), -1); 65 | t.equal(stderr, ''); 66 | 67 | t.end(); 68 | }); 69 | } 70 | 71 | /* 72 | * This function checks expected output from `sdcadm insts` and 73 | * `sdcadm health`, which share approximately eighty percent. 74 | * 75 | * The expected opts argument includes the output from those 76 | * commands, server hostnames associated with server uuids and 77 | * service names associated with service uuids. 78 | */ 79 | function checkInsts(t, opts, cb) { 80 | var inputs = opts.inputs; 81 | var serviceNamesFromUUID = opts.serviceNamesFromUUID; 82 | var serverHostnamesFromUUID = opts.serverHostnamesFromUUID; 83 | 84 | vasync.forEachPipeline({ 85 | func: function (item, next) { 86 | 87 | if (item.service === 'global' || item.instance === '-') { 88 | next(); 89 | return; 90 | } 91 | 92 | var description = (item.alias !== '-') ? 93 | util.format('%s (%s)', item.alias, item.instance) : 94 | util.format('%s (%s)', item.instance, item.service); 95 | t.comment(util.format('checking %s in %s', 96 | description, item.hostname)); 97 | 98 | 99 | 100 | var cmd2 = 'sdc-sapi /instances/' + item.instance + ' | json -H'; 101 | exec(cmd2, function (err2, stdout2, stderr2) { 102 | t.ifError(err2, 'no SAPI error'); 103 | var instanceDetails = parseJsonOut(stdout2); 104 | if (!instanceDetails) { 105 | t.ok(false, 'failed to parse JSON for cmd ' + cmd2); 106 | next(); 107 | return; 108 | } 109 | 110 | if (item.service !== 'assets') { 111 | t.equal(serviceNamesFromUUID[instanceDetails.service_uuid], 112 | item.service, 'service should match'); 113 | } 114 | 115 | if (item.alias === '-') { 116 | next(); 117 | return; 118 | } 119 | 120 | var cmd = 'sdc-vmapi /vms/' + item.instance + ' | json -H'; 121 | exec(cmd, function (err, stdout, stderr) { 122 | t.ifError(err, 'no VMAPI error'); 123 | 124 | var vmDetails = parseJsonOut(stdout); 125 | if (!vmDetails) { 126 | t.ok(false, 'failed to parse JSON for cmd ' + cmd); 127 | next(); 128 | return; 129 | } 130 | 131 | t.equal(vmDetails.uuid, item.instance, 132 | 'uuid should match'); 133 | t.equal(vmDetails.alias, item.alias, 134 | 'alias should match'); 135 | 136 | t.equal(serverHostnamesFromUUID[vmDetails.server_uuid], 137 | item.hostname, 'server hostname should match'); 138 | 139 | t.notEqual(vmDetails.state, 'failed', 140 | 'check state for VM ' + item.instance); 141 | 142 | if (item.version) { 143 | var imgUuid = vmDetails.image_uuid; 144 | var cmd3 = 'sdc-imgapi /images/' + imgUuid + 145 | ' | json -H'; 146 | 147 | exec(cmd3, function (err3, stdout3, stderr3) { 148 | t.ifError(err3, 'IMGAPI call error'); 149 | 150 | var imgInfo = parseJsonOut(stdout3); 151 | if (!imgInfo) { 152 | t.ok(false, 'failed to parse JSON for cmd ' + 153 | cmd3); 154 | next(); 155 | return; 156 | } 157 | 158 | t.equal(imgInfo.version, item.version, 159 | 'check version for VM ' + vmDetails.uuid); 160 | 161 | next(); 162 | }); 163 | } else { 164 | next(); 165 | } 166 | }); 167 | }); 168 | }, 169 | inputs: inputs 170 | }, function (resErr) { 171 | t.ifError(resErr); 172 | cb(); 173 | }); 174 | } 175 | 176 | 177 | module.exports = { 178 | DEFAULT_VM_SERVICES: DEFAULT_VM_SERVICES, 179 | ALL_VM_SERVICES: ALL_VM_SERVICES, 180 | DEFAULT_AGENT_SERVICES: DEFAULT_AGENT_SERVICES, 181 | ALL_AGENT_SERVICES: ALL_AGENT_SERVICES, 182 | UUID_RE: UUID_RE, 183 | checkHelp: checkHelp, 184 | deepCopy: deepCopy, 185 | parseJsonOut: parseJsonOut, 186 | parseTextOut: parseTextOut, 187 | checkInsts: checkInsts 188 | }; 189 | -------------------------------------------------------------------------------- /test/common.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2016 Joyent, Inc. 9 | */ 10 | 11 | var test = require('tape').test; 12 | var common = require('../lib/common.js'); 13 | 14 | test('safeCycles', function (t) { 15 | var a = { 16 | foo: { 17 | bar: null 18 | } 19 | }; 20 | a.foo.bar = a; 21 | 22 | t.throws(function () { 23 | JSON.stringify(a); 24 | }, TypeError); 25 | 26 | t.doesNotThrow(function () { 27 | JSON.stringify(a, common.safeCycles()); 28 | }, TypeError); 29 | 30 | t.end(); 31 | }); 32 | -------------------------------------------------------------------------------- /test/dc-maint.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018, Joyent, Inc. 9 | */ 10 | 11 | 12 | var test = require('tape').test; 13 | 14 | var exec = require('child_process').exec; 15 | var util = require('util'); 16 | 17 | var shared = require('./shared'); 18 | 19 | var DC_MAINT_START_TIME; 20 | 21 | function checkHelp(t, subCmd, expectedStr) { 22 | var cmd = 'sdcadm dc-maint help ' + subCmd; 23 | 24 | exec(cmd, function (err, stdout, stderr) { 25 | t.ifError(err, util.format('cmd \'%s\' error', cmd)); 26 | t.notEqual(stdout.indexOf(expectedStr), -1, 'Expected stdout'); 27 | t.equal(stderr, '', 'Empty stderr'); 28 | 29 | t.end(); 30 | }); 31 | } 32 | 33 | test('setup', function (t) { 34 | // Need to set it some time in the future or dc-maint start will fail: 35 | var d = new Date(); 36 | var time = d.setHours(d.getHours() + 1); 37 | DC_MAINT_START_TIME = new Date(time).toISOString(); 38 | shared.prepare(t, {docker: true}); 39 | }); 40 | 41 | 42 | test('sdcadm dc-maint help', function (t) { 43 | checkHelp(t, '', 'Show and modify the DC maintenance mode'); 44 | }); 45 | 46 | 47 | test('sdcadm dc-maint start help', function (t) { 48 | checkHelp(t, 'start', 'Start maintenance mode'); 49 | }); 50 | 51 | 52 | test('sdcadm dc-maint stop help', function (t) { 53 | checkHelp(t, 'stop', 'Stop maintenance mode'); 54 | }); 55 | 56 | 57 | test('sdcadm dc-maint status help', function (t) { 58 | checkHelp(t, 'status', 'Show maintenance status'); 59 | }); 60 | 61 | // --message='Daily Maintenance Time' --eta=`date -u '+%Y-%m-%dT%H:%M:%S'` 62 | /* 63 | * Note that in case of not having either cloudapi or docker instances 64 | * this test would fail b/c the related strings would not be matched and 65 | * instead, the output would be: 66 | * 67 | * "No docker instances to update" 68 | * "No cloudapi instances to update" 69 | * 70 | * But, given that on that case putting the DC on maintenance will have no 71 | * sense, we'll just assume we have cloudapi and docker installed. 72 | * 73 | */ 74 | test('sdcadm dc-maint start', function (t) { 75 | 76 | var cmd = 'sdcadm dc-maint start --message="Maintenance time" ' + 77 | '--eta=' + DC_MAINT_START_TIME; 78 | exec(cmd, function (err, stdout, stderr) { 79 | t.ifError(err, 'Execution error'); 80 | t.equal(stderr, '', 'Empty stderr'); 81 | 82 | var expectedStr = [ 83 | 'Putting cloudapi in read-only mode', 84 | 'Putting docker in read-only mode', 85 | 'Waiting up to 5 minutes for workflow jobs to drain', 86 | 'Workflow cleared of running and queued jobs' 87 | ]; 88 | 89 | expectedStr.forEach(function (str) { 90 | t.notEqual(stdout.indexOf(str), -1, 'Match: ' + str); 91 | }); 92 | 93 | // check jobs are drained 94 | var cmd2 = 'sdc-workflow /jobs | json -Ha execution'; 95 | exec(cmd2, function (err2, stdout2, stderr2) { 96 | t.ifError(err2, 'Execution error'); 97 | t.equal(stderr2, '', 'Empty stderr'); 98 | 99 | t.ifError(stdout2.match('queued'), 'jobs still queued'); 100 | t.ifError(stdout2.match('running'), 'jobs still running'); 101 | 102 | t.end(); 103 | }); 104 | }); 105 | }); 106 | 107 | 108 | test('sdcadm dc-maint status (maintenance)', function (t) { 109 | exec('sdcadm dc-maint status', function (err, stdout, stderr) { 110 | t.ifError(err); 111 | t.equal(stderr, ''); 112 | 113 | var match = stdout.match(/DC maintenance ETA: (.+)/); 114 | t.ok(match); 115 | 116 | var started = match[1]; 117 | t.equal(started, DC_MAINT_START_TIME); 118 | 119 | t.end(); 120 | }); 121 | }); 122 | 123 | 124 | test('sdcadm dc-maint status --json', function (t) { 125 | exec('sdcadm dc-maint status --json', function (err, stdout, stderr) { 126 | t.ifError(err); 127 | t.equal(stderr, ''); 128 | 129 | var status = JSON.parse(stdout); 130 | t.deepEqual(Object.keys(status).sort(), 131 | ['cloudapiMaint', 'dockerMaint', 'eta', 132 | 'maint', 'message', 'startTime']); 133 | 134 | t.equal(status.maint, true); 135 | t.equal(status.cloudapiMaint, true); 136 | t.equal(status.dockerMaint, true); 137 | t.equal(status.eta, DC_MAINT_START_TIME); 138 | 139 | t.end(); 140 | }); 141 | }); 142 | 143 | 144 | test('sdcadm dc-maint stop', function (t) { 145 | exec('sdcadm dc-maint stop', function (err, stdout, stderr) { 146 | t.ifError(err); 147 | 148 | t.notEqual(stdout.indexOf('Taking cloudapi out of read-only mode\n' + 149 | 'Taking docker out of read-only mode'), -1); 150 | t.equal(stderr, ''); 151 | 152 | t.end(); 153 | }); 154 | }); 155 | 156 | 157 | test('sdcadm dc-maint status (no maintenance)', function (t) { 158 | exec('sdcadm dc-maint status', function (err, stdout, stderr) { 159 | t.ifError(err); 160 | 161 | t.notEqual(stdout.indexOf('DC maintenance: off'), -1); 162 | t.equal(stderr, ''); 163 | 164 | t.end(); 165 | }); 166 | }); 167 | 168 | 169 | test('sdcadm dc-maint status --json (no maintenance)', function (t) { 170 | exec('sdcadm dc-maint status --json', function (err, stdout, stderr) { 171 | t.ifError(err); 172 | t.equal(stderr, ''); 173 | 174 | var status = JSON.parse(stdout); 175 | t.deepEqual(Object.keys(status).sort(), 176 | ['cloudapiMaint', 'dockerMaint', 177 | 'maint']); 178 | 179 | t.equal(status.maint, false); 180 | t.equal(status.cloudapiMaint, false); 181 | t.equal(status.dockerMaint, false); 182 | 183 | t.end(); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/help.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2016, Joyent, Inc. 9 | */ 10 | 11 | 12 | var test = require('tape').test; 13 | var exec = require('child_process').exec; 14 | var checkHelp = require('./common').checkHelp; 15 | 16 | 17 | test('sdcadm --help', function (t) { 18 | exec('sdcadm --help', function (err, stdout, stderr) { 19 | t.ifError(err); 20 | 21 | t.notEqual(stdout.indexOf('sdcadm [OPTIONS] COMMAND [ARGS...]'), -1); 22 | t.equal(stderr, ''); 23 | 24 | t.end(); 25 | }); 26 | }); 27 | 28 | 29 | test('sdcadm help', function (t) { 30 | checkHelp(t, '', 'sdcadm help COMMAND'); 31 | }); 32 | 33 | 34 | test('sdcadm help self-update', function (t) { 35 | checkHelp(t, 'self-update', 'sdcadm self-update --latest []'); 36 | }); 37 | 38 | 39 | test('sdcadm help instances', function (t) { 40 | checkHelp(t, 'instances', 'sdcadm instances []'); 41 | }); 42 | 43 | 44 | test('sdcadm help insts', function (t) { 45 | checkHelp(t, 'insts', 'sdcadm instances []'); 46 | }); 47 | 48 | 49 | test('sdcadm help services', function (t) { 50 | checkHelp(t, 'services', 'sdcadm services []'); 51 | }); 52 | 53 | 54 | test('sdcadm help update', function (t) { 55 | checkHelp(t, 'update', 'sdcadm update []'); 56 | }); 57 | 58 | 59 | test('sdcadm help up', function (t) { 60 | checkHelp(t, 'up', 'sdcadm update []'); 61 | }); 62 | 63 | 64 | test('sdcadm help rollback', function (t) { 65 | checkHelp(t, 'rollback', 'sdcadm rollback []'); 66 | }); 67 | 68 | 69 | test('sdcadm help create', function (t) { 70 | checkHelp(t, 'create', 'sdcadm create SERVICE'); 71 | }); 72 | 73 | 74 | test('sdcadm help check-config', function (t) { 75 | checkHelp(t, 'check-config', 'sdcadm check-config []'); 76 | }); 77 | 78 | 79 | test('sdcadm help check-health', function (t) { 80 | checkHelp(t, 'check-health', 'sdcadm check-health []'); 81 | }); 82 | 83 | 84 | test('sdcadm help post-setup', function (t) { 85 | checkHelp(t, 'post-setup', 'sdcadm post-setup [OPTIONS] COMMAND'); 86 | }); 87 | 88 | 89 | test('sdcadm help platform', function (t) { 90 | checkHelp(t, 'platform', 'sdcadm platform [OPTIONS] COMMAND'); 91 | }); 92 | 93 | 94 | test('sdcadm help experimental', function (t) { 95 | checkHelp(t, 'experimental', 'sdcadm experimental [OPTIONS] COMMAND'); 96 | }); 97 | -------------------------------------------------------------------------------- /test/procedures-shared.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2022 Cloudcontainers B.V. 9 | */ 10 | 11 | var test = require('tape').test; 12 | var shared = require('../lib/procedures/shared.js'); 13 | 14 | test('nextIdNoInstances', function (t) { 15 | var arg = { 16 | change: { 17 | service: { 18 | name: 'portolan' 19 | } 20 | }, 21 | instances: [] 22 | }; 23 | 24 | arg = shared.getNextInstAliasOrdinal(arg); 25 | 26 | t.equal(arg.nextId, 0); 27 | 28 | t.end(); 29 | }); 30 | 31 | test('nextIdSingleInstance', function (t) { 32 | var arg = { 33 | change: { 34 | service: { 35 | name: 'portolan' 36 | } 37 | }, 38 | instances: [ 39 | { 40 | params: { 41 | alias: 'portolan0' 42 | } 43 | } 44 | ] 45 | }; 46 | 47 | arg = shared.getNextInstAliasOrdinal(arg); 48 | 49 | t.equal(arg.nextId, 1); 50 | 51 | t.end(); 52 | }); 53 | 54 | // Verify instances are sorted numerically instead of as strings 55 | // 10 > 7 while '10' < '7' 56 | // Allows instances to go up to 11 (and beyond) 57 | test('nextIdInstance10', function (t) { 58 | var arg = { 59 | change: { 60 | service: { 61 | name: 'portolan' 62 | } 63 | }, 64 | instances: [ 65 | { 66 | params: { 67 | alias: 'portolan0' 68 | } 69 | }, 70 | { 71 | params: { 72 | alias: 'portolan10' 73 | } 74 | }, 75 | { 76 | params: { 77 | alias: 'portolan7' 78 | } 79 | } 80 | ] 81 | }; 82 | 83 | arg = shared.getNextInstAliasOrdinal(arg); 84 | 85 | t.equal(arg.nextId, 11); 86 | 87 | t.end(); 88 | }); 89 | -------------------------------------------------------------------------------- /test/runtests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright 2019, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Run `sdcadm` integration tests. 14 | # This is expected to be run from GZ. 15 | # 16 | # This creates .tap files in OUTPUT_DIR that can be processed by a TAP reader. 17 | # Testing config and log files are also placed in this dir. 18 | # 19 | # Run `./runtests -h` for usage info. 20 | # 21 | 22 | if [ "$TRACE" != "" ]; then 23 | export PS4='${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 24 | set -o xtrace 25 | fi 26 | set -o errexit 27 | set -o pipefail 28 | 29 | #---- guard 30 | 31 | guard_file=/lib/sdc/.sdc-test-no-production-data 32 | if [[ ! -f "$guard_file" ]]; then 33 | cat <> $FAILING_LIST 143 | [[ -n "$opt_stop_on_failure" ]] && break 144 | fi 145 | done 146 | 147 | set -o errexit 148 | 149 | 150 | # Summarize results 151 | echo "" 152 | echo "# Test output in $RESULTS:" 153 | ls -1 $RESULTS/*.tap | sed -e 's,^,# ,' 154 | 155 | num_failing_test_files=$(cat ${FAILING_LIST} | wc -l) 156 | if [[ ${num_failing_test_files} -gt 0 ]]; then 157 | echo "" 158 | echo "# Failing test files:" 159 | cat $FAILING_LIST | sed -e 's,^,# ,' 160 | exit 1 161 | else 162 | exit 0 163 | fi 164 | -------------------------------------------------------------------------------- /test/sdcadm.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2016, Joyent, Inc. 9 | */ 10 | 11 | 12 | var exec = require('child_process').exec; 13 | var format = require('util').format; 14 | var test = require('tape').test; 15 | 16 | 17 | test('sdcadm', function (t) { 18 | exec('sdcadm', function (err, stdout, stderr) { 19 | t.ok(err, 'usage error'); 20 | t.equal(err.code, 1); 21 | 22 | t.ok(stdout.match('Usage')); 23 | t.equal(stderr, ''); 24 | 25 | t.end(); 26 | }); 27 | }); 28 | 29 | 30 | test('sdcadm --help', function (t) { 31 | exec('sdcadm --help', function (err, stdout, stderr) { 32 | t.ifError(err, 'no help error'); 33 | 34 | t.ok(stdout.match('Usage')); 35 | t.equal(stderr, ''); 36 | 37 | t.end(); 38 | }); 39 | }); 40 | 41 | 42 | test('sdcadm --version', function (t) { 43 | exec('sdcadm --version', function (err, stdout, stderr) { 44 | t.ifError(err, 'no version error'); 45 | var verRe = /^sdcadm \d+\.\d+\.\d+ \(.+-\d+T\d+Z-g[0-9a-f]{7}\)$/; 46 | t.ok(verRe.test(stdout.trim()), 47 | format('version should match %s: %j', verRe, stdout.trim())); 48 | t.equal(stderr, ''); 49 | 50 | t.end(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/self-update.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | * Copyright 2022 MNX Cloud, Inc. 10 | */ 11 | 12 | /* 13 | * NB: these tests require the sdcadm version under test to be available 14 | * from updates.tritondatacenter.com (via a jenkins build). 15 | */ 16 | 17 | var test = require('tape').test; 18 | var exec = require('child_process').exec; 19 | var fs = require('fs'); 20 | var assert = require('assert-plus'); 21 | 22 | var CURRENT_VERSION = null; 23 | var LATEST_UUID = null; 24 | 25 | const UPDATES_URL = process.env.UPDATES_IMGADM_URL || 26 | 'https://updates.tritondatacenter.com/'; 27 | 28 | function checkUpdateResults(t, err, stdout, stderr, moreStrings) { 29 | if (moreStrings) { 30 | assert.arrayOfString(moreStrings, 'moreStrings'); 31 | } 32 | 33 | t.ifError(err); 34 | t.equal(stderr, ''); 35 | 36 | if (stdout.indexOf('Already up-to-date') !== -1) { 37 | t.end(); 38 | return; 39 | } 40 | 41 | var findStrings = [ 42 | 'Update to sdcadm', 43 | 'Download update from', 44 | 'Run sdcadm installer', 45 | 'Updated to sdcadm' 46 | ]; 47 | 48 | if (moreStrings) { 49 | findStrings = findStrings.concat(moreStrings); 50 | } 51 | 52 | findStrings.forEach(function (str) { 53 | t.ok(stdout.match(str), 'check update string present'); 54 | }); 55 | 56 | t.end(); 57 | } 58 | 59 | function getSdcadmBuildstampVersion(t, cb) { 60 | fs.readFile('/opt/smartdc/sdcadm/etc/buildstamp', { 61 | encoding: 'utf8' 62 | }, function (err, data) { 63 | t.ifError(err); 64 | t.ok(data); 65 | cb(data.trim()); 66 | }); 67 | } 68 | 69 | function getSdcadmChannel(t, cb) { 70 | if (CURRENT_VERSION === '') { 71 | cb(); 72 | return; 73 | } 74 | var command = 'updates-imgadm get ' + CURRENT_VERSION + 75 | ' -C \'*\' | json channels[0]'; 76 | exec(command, function (err, stdout, stderr) { 77 | t.ifError(err, 'getSdcadmChannel error'); 78 | t.equal(stderr, '', 'getSdcadmChannel stderr'); 79 | t.ok(stdout, 'getSdcadmChannel stdout'); 80 | cb(stdout.trim()); 81 | }); 82 | } 83 | 84 | 85 | test('setup', function (t) { 86 | getSdcadmBuildstampVersion(t, function (data) { 87 | var updatesCmd = '/opt/smartdc/bin/updates-imgadm list ' + 88 | 'tag.buildstamp=' + data + ' --latest -o uuid -H -C \'*\''; 89 | exec(updatesCmd, function (err2, stdout, stderr) { 90 | t.ifError(err2); 91 | CURRENT_VERSION = stdout.trim(); 92 | t.ok(CURRENT_VERSION); 93 | t.equal(stderr, ''); 94 | var updatesCmd2 = '/opt/smartdc/bin/updates-imgadm list ' + 95 | 'name=sdcadm --latest -o uuid -H'; 96 | exec(updatesCmd2, function (err3, stdout2, stderr2) { 97 | t.ifError(err3); 98 | LATEST_UUID = stdout.trim(); 99 | t.ok(LATEST_UUID); 100 | t.equal(stderr, ''); 101 | t.end(); 102 | }); 103 | }); 104 | }); 105 | }); 106 | 107 | test('sdcadm self-update --help', function (t) { 108 | exec('sdcadm self-update --help', function (err, stdout, stderr) { 109 | t.ifError(err); 110 | t.notEqual(stdout.indexOf('sdcadm self-update --latest []'), 111 | -1); 112 | t.equal(stderr, ''); 113 | t.end(); 114 | }); 115 | }); 116 | 117 | 118 | test('sdcadm self-update --latest --dry-run', function (t) { 119 | exec('sdcadm self-update --latest --dry-run', 120 | function (err, stdout, stderr) { 121 | checkUpdateResults(t, err, stdout, stderr); 122 | }); 123 | }); 124 | 125 | 126 | test('sdcadm self-update --allow-major-update', function (t) { 127 | exec('sdcadm self-update --allow-major-update --dry-run --latest', 128 | function (err, stdout, stderr) { 129 | checkUpdateResults(t, err, stdout, stderr); 130 | }); 131 | }); 132 | 133 | 134 | test('sdcadm self-update --latest --channel=staging', function (t) { 135 | var cmd = 'sdcadm self-update --latest --channel=staging'; 136 | exec(cmd, function (err, stdout, stderr) { 137 | checkUpdateResults(t, err, stdout, stderr, ['Using channel staging']); 138 | }); 139 | }); 140 | 141 | 142 | test('sdcadm self-update --latest', function (t) { 143 | var cmd = 'sdcadm self-update --latest --channel=dev'; 144 | exec(cmd, function (err, stdout, stderr) { 145 | checkUpdateResults(t, err, stdout, stderr, ['Using channel dev']); 146 | }); 147 | }); 148 | 149 | 150 | test('sdcadm self-update -S ... --latest', function (t) { 151 | var cmd = 'sdcadm self-update -S ' + UPDATES_URL + 152 | ' --latest --channel=dev'; 153 | exec(cmd, function (err, stdout, stderr) { 154 | checkUpdateResults(t, err, stdout, stderr, ['Using channel dev']); 155 | }); 156 | }); 157 | 158 | test('sdcadm self-update IMAGE_UUID', function (t) { 159 | getSdcadmChannel(t, function (channel) { 160 | var cmd = 'sdcadm self-update ' + CURRENT_VERSION + ' -C ' + channel; 161 | exec(cmd, function (err, stdout, stderr) { 162 | checkUpdateResults(t, err, stdout, stderr, 163 | ['Using channel ' + channel]); 164 | }); 165 | }); 166 | }); 167 | 168 | test('sdcadm self-update -S ... IMAGE_UUID', function (t) { 169 | getSdcadmChannel(t, function (channel) { 170 | var cmd = 'sdcadm self-update -S ' + UPDATES_URL + 171 | ' -C ' + channel + ' ' + CURRENT_VERSION; 172 | exec(cmd, function (err, stdout, stderr) { 173 | checkUpdateResults(t, err, stdout, stderr, 174 | ['Using channel ' + channel]); 175 | }); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /test/shared.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * We want to be able to test some sdcadm subcommands (most of them part of 13 | * the post-setup subcommands) which are usually required before we can run 14 | * any other commands. If not used in a previous set of tests, these will run as 15 | * part of the post-setup test suite. 16 | */ 17 | 18 | var vasync = require('vasync'); 19 | var assert = require('assert-plus'); 20 | 21 | var exec = require('child_process').exec; 22 | var common = require('./common'); 23 | 24 | // If there's an error here, the smarter thing to do is to just fail 25 | // the whole process and avoid running any more tests, since those 26 | // requiring IMGAPI external NIC will fail: 27 | function haveCommonExternalNics(t, cb) { 28 | assert.func(cb, 'cb'); 29 | var externalNicsExist = false; 30 | var cmd = 'sdc-vmapi /vms?query=\'(|(alias=adminui*)(alias=imgapi*))\'|' + 31 | 'json -H'; 32 | exec(cmd, function haveNicsCb(err, stdout, stderr) { 33 | t.ifError(err, 'Execution error'); 34 | t.equal(stderr, '', 'Empty stderr'); 35 | var vms = common.parseJsonOut(stdout); 36 | vms = vms.filter(function alreadyHasExternalNic(vm) { 37 | return vm.nics.some(function (nic) { 38 | return nic.nic_tag === 'external'; 39 | }); 40 | }); 41 | if (vms.length) { 42 | externalNicsExist = true; 43 | } 44 | cb(err, externalNicsExist); 45 | }); 46 | } 47 | 48 | // TODO: check instances using either VMAPI or SAPI here so we can reach 49 | // instances on other servers too. 50 | function getNumInsts(svc, cb) { 51 | assert.string(svc, 'svc'); 52 | assert.func(cb, 'cb'); 53 | exec('vmadm lookup alias=~"^' + svc + '"', function lookupCb(err, stdout) { 54 | if (err) { 55 | cb(err); 56 | return; 57 | } 58 | var lines = stdout.split('\n').filter(function (l) { 59 | return (l !== ''); 60 | }); 61 | cb(null, lines.length); 62 | }); 63 | } 64 | 65 | 66 | /* 67 | * `requirements` is expected to be an object including the following members: 68 | * { 69 | * external_nics: true | false, 70 | * cloudapi: true | false, 71 | * docker: true | false 72 | * } 73 | */ 74 | function prepare(t, requirements) { 75 | assert.object(requirements, 'requirements'); 76 | assert.optionalBool(requirements.external_nics, 77 | 'requirements.external_nics'); 78 | assert.optionalBool(requirements.cloudapi, 'requirements.cloudapi'); 79 | assert.optionalBool(requirements.docker, 'requirements.docker'); 80 | 81 | // We need to download images in order to be able to setup docker: 82 | if (requirements.docker) { 83 | requirements.external_nics = true; 84 | } 85 | vasync.pipeline({ 86 | funcs: [ 87 | function prepareExternalNics(_, next) { 88 | if (!requirements.external_nics) { 89 | next(); 90 | return; 91 | } 92 | haveCommonExternalNics(t, function cb(err, externalNicsExist) { 93 | if (err) { 94 | next(err); 95 | return; 96 | } 97 | if (externalNicsExist) { 98 | next(); 99 | return; 100 | } 101 | var cmd = 'sdcadm post-setup common-external-nics'; 102 | exec(cmd, function execCb(err2, stdout, stderr) { 103 | t.equal(stderr, '', 'Empty stderr'); 104 | next(err2); 105 | }); 106 | }); 107 | }, 108 | function prepareCloudapi(_, next) { 109 | if (!requirements.cloudapi) { 110 | next(); 111 | return; 112 | } 113 | getNumInsts('cloudapi', function numInstsCb(err, numInsts) { 114 | if (err) { 115 | next(err); 116 | return; 117 | } 118 | if (numInsts > 0) { 119 | next(); 120 | return; 121 | } 122 | var cmd = 'sdcadm post-setup cloudapi'; 123 | exec(cmd, function execCb(err2, stdout, stderr) { 124 | t.equal(stderr, '', 'Empty stderr'); 125 | next(err2); 126 | }); 127 | }); 128 | }, 129 | function prepareDocker(_, next) { 130 | if (!requirements.docker) { 131 | next(); 132 | return; 133 | } 134 | getNumInsts('docker', function numInstsCb(err, numInsts) { 135 | if (err) { 136 | next(err); 137 | return; 138 | } 139 | if (numInsts > 0) { 140 | next(); 141 | return; 142 | } 143 | var cmd = 'sdcadm post-setup docker'; 144 | exec(cmd, function execCb(err2, stdout, stderr) { 145 | t.equal(stderr, '', 'Empty stderr'); 146 | next(err2); 147 | }); 148 | }); 149 | } 150 | ] 151 | }, function pipeCb(err) { 152 | t.end(err); 153 | }); 154 | 155 | } 156 | 157 | module.exports = { 158 | haveCommonExternalNics: haveCommonExternalNics, 159 | getNumInsts: getNumInsts, 160 | prepare: prepare 161 | }; 162 | // vim: set softtabstop=4 shiftwidth=4: 163 | -------------------------------------------------------------------------------- /test/unit/steps/servers.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2019 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * Test parts of lib/steps/servers.js. 13 | */ 14 | 15 | 'use strict'; 16 | 17 | const test = require('tap').test; 18 | 19 | const stepsServers = require('../../../lib/steps/servers'); 20 | 21 | 22 | test('steps.servers.ensureServersRunning', function (suite) { 23 | const ensureServersRunning = stepsServers.ensureServersRunning; 24 | 25 | suite.test('no servers', function (t) { 26 | ensureServersRunning({servers: []}, function (err) { 27 | t.ifError(err); 28 | t.end(); 29 | }); 30 | }); 31 | 32 | suite.test('status=running', function (t) { 33 | ensureServersRunning({ 34 | servers: [ 35 | { 36 | uuid: 'fffe64de-41e1-11e9-96ab-c31da21ea778', 37 | hostname: 'TESTHOST0', 38 | status: 'running' 39 | }, 40 | { 41 | uuid: '0315187a-41e2-11e9-9c21-37ad3dd66f42', 42 | hostname: 'TESTHOST1', 43 | status: 'running' 44 | } 45 | ] 46 | }, function (err) { 47 | t.ifError(err); 48 | t.end(); 49 | }); 50 | }); 51 | 52 | suite.test('status=unknown should error', function (t) { 53 | ensureServersRunning({ 54 | servers: [ 55 | { 56 | uuid: 'fffe64de-41e1-11e9-96ab-c31da21ea778', 57 | hostname: 'TESTHOST0', 58 | status: 'unknown' 59 | }, 60 | { 61 | uuid: '0315187a-41e2-11e9-9c21-37ad3dd66f42', 62 | hostname: 'TESTHOST1', 63 | status: 'running' 64 | } 65 | ] 66 | }, function (err) { 67 | t.ok(err, 'expected error that TESTHOST0 is not running'); 68 | t.ok(err.message.includes('not running'), 69 | 'error message includes "not running": ' + err.message); 70 | t.ok(err.message.includes('TESTHOST0'), 71 | 'error message includes "TESTHOST0": ' + err.message); 72 | t.end(); 73 | }); 74 | }); 75 | 76 | suite.end(); 77 | }); 78 | -------------------------------------------------------------------------------- /test/unit/testutil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 'use strict'; 11 | 12 | const bunyan = require('bunyan'); 13 | 14 | function TestCommentStream(test) { 15 | this.test = test; 16 | } 17 | 18 | TestCommentStream.prototype.write = function write(rec) { 19 | this.test.comment(rec); 20 | }; 21 | 22 | function createBunyanLogger(test) { 23 | return bunyan.createLogger( 24 | {name: 'unit', 25 | streams: [{type: 'raw', 26 | stream: new TestCommentStream(test)}]}); 27 | } 28 | 29 | module.exports = { 30 | createBunyanLogger: createBunyanLogger 31 | }; 32 | -------------------------------------------------------------------------------- /tools/gen-sample-packages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """A crack at generating a *sample* set of SDC packages 4 | 5 | Example run: 6 | 7 | $ npm install -g tabula json 8 | $ python gen-sample-pkgs.py | json -aj name max_physical_memory max_swap quota cpu_cap fss vcpu description | tabula -s max_physical_memory -s name name max_physical_memory max_swap quota cpu_cap fss vcpu description 9 | NAME MAX_PHYSICAL_MEMORY MAX_SWAP QUOTA CPU_CAP FSS VCPUS DESCRIPTION 10 | sample-0.25-kvm 256 512 4096 20 20 1 Sample 0.25 GB RAM, 4 GB Disk 11 | sample-0.25-smartos 256 512 4096 20 20 - Sample 0.25 GB RAM, 4 GB Disk 12 | sample-0.5-kvm 512 1024 8192 20 20 1 Sample 0.5 GB RAM, 8 GB Disk 13 | sample-0.5-smartos 512 1024 8192 20 20 - Sample 0.5 GB RAM, 8 GB Disk 14 | sample-1.0-kvm 1024 2048 16384 20 20 1 Sample 1 GB RAM, 16 GB Disk 15 | sample-1.0-smartos 1024 2048 16384 20 20 - Sample 1 GB RAM, 16 GB Disk 16 | sample-4.0-kvm 4096 8192 65536 50 50 1 Sample 4 GB RAM, 64 GB Disk 17 | sample-4.0-smartos 4096 8192 65536 50 50 - Sample 4 GB RAM, 64 GB Disk 18 | sample-8.0-kvm 8192 16384 131072 100 100 1 Sample 8 GB RAM, 128 GB Disk 19 | sample-8.0-smartos 8192 16384 131072 100 100 - Sample 8 GB RAM, 128 GB Disk 20 | sample-16.0-kvm 16384 32768 262144 200 200 2 Sample 16 GB RAM, 256 GB Disk 21 | sample-16.0-smartos 16384 32768 262144 200 200 - Sample 16 GB RAM, 256 GB Disk 22 | 23 | Notes: 24 | - With the exception of the cpu_cap=20 values (see below), this will 25 | linear-fast-fit fill a Richmond-A or TL-A compute node. 26 | - Set a min cpu_cap at 20 to avoid some very small values for the smaller 27 | sized packages. Implication: This will result in CPU over provisioning if have 28 | lots of the smaller packages on a CN. So be it. I'll doc that on the command 29 | that adds these sample packages. 30 | 31 | 32 | These can then be installed into PAPI from the SDC headnode GZ with this 33 | command: 34 | 35 | json -f /opt/smartdc/sdcadm/etc/sample-packages.json -a -o jsony-0 \ 36 | | while read pkg; do echo "$pkg" | sdc-papi /packages -X POST -d@-; done 37 | """ 38 | 39 | 40 | import sys 41 | import json 42 | import copy 43 | 44 | top = { 45 | "name": "sample-16-smartos", 46 | "version": "1.0.0", 47 | "active": True, 48 | "cpu_cap": 200, 49 | "default": False, 50 | "max_lwps": 4000, 51 | "max_physical_memory": 16384, 52 | "max_swap": 32768, 53 | "quota": 262144, 54 | "zfs_io_priority": 100, 55 | "group": "Sample", 56 | "description": "Sample 16 GB RAM, 256 GB Disk", 57 | "v": 1 58 | } 59 | 60 | 61 | # Keith: """Basically I'd take the 16GB/2CPU/256GB instance and divide it by 2, 62 | # 4, 8, and 16. Then you get optimal designation cost and zero waste. I do 63 | # question whether a 0.125 CPU instance is worth using, but who knows. So, those 64 | # instances (and linear multiples of them) will linear-fast-fit fill a 65 | # Richmond-A or TL-A.""" 66 | # 67 | # Unfortunately this results in silly low cpu_cap for the smaller RAM pkgs. 68 | # There is no happy answer here at reasonable scale. So for now we punt 69 | # and have a lower cpu_cap lower bound of 20. This effectively means that with 70 | # a lot of the smaller zones on a server will mean CPU overprovisioning. 71 | pkgs = [] 72 | for ram_gb in [0.25, 0.5, 1., 4., 8., 16.]: 73 | pkg = copy.copy(top) 74 | ram_mb = ram_gb * 1024 75 | factor = top["max_physical_memory"] / ram_mb 76 | #print factor 77 | for field in ["cpu_cap", "max_physical_memory", "max_swap", "quota"]: 78 | pkg[field] = top[field] / factor 79 | if pkg["cpu_cap"] < 20: 80 | pkg["cpu_cap"] = 20 81 | else: 82 | pkg["cpu_cap"] = round(pkg["cpu_cap"]) 83 | pkg["fss"] = pkg["cpu_cap"] # cpu_shares 84 | pkg["name"] = "sample-%s-smartos" % ram_gb 85 | 86 | ram_str = pkg["max_physical_memory"] / 1024.0 87 | if ram_str == int(ram_str): 88 | ram_str = str(int(ram_str)) 89 | pkg["description"] = "Sample %s GB RAM, %s GB Disk" % ( 90 | ram_str, 91 | int(pkg["quota"] / 1024) 92 | ) 93 | pkgs.append(pkg) 94 | 95 | pkg_kvm = copy.copy(pkg) 96 | pkg_kvm["vcpus"] = max(1, int(pkg_kvm["cpu_cap"] / 100.0)) 97 | pkg_kvm["name"] = "sample-%s-kvm" % ram_gb 98 | pkgs.append(pkg_kvm) 99 | 100 | print json.dumps(pkgs, indent=4) 101 | -------------------------------------------------------------------------------- /tools/install-sdcadm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # This is the script included in sdcadm tarballs to handle 14 | # the sdcadm install/upgrade on a headnode GZ. 15 | # 16 | # Usage: 17 | # install-sdcadm.sh # in the extracted shar dir 18 | # 19 | # Environment: 20 | # SDCADM_WRKDIR= 21 | # If not provided, then details, including rollback info, will be 22 | # put in "/var/sdcadm/self-updates/YYYYMMDDTHHMMSSZ". 23 | # 24 | 25 | if [[ -n "$TRACE" ]]; then 26 | export PS4='[\D{%FT%TZ}] ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 27 | set -o xtrace 28 | fi 29 | set -o errexit 30 | set -o pipefail 31 | 32 | TOP=$(cd $(dirname $0)/; pwd) 33 | DESTDIR=/opt/smartdc/sdcadm 34 | NEWDIR=$DESTDIR.new 35 | OLDDIR=$DESTDIR.old 36 | if [[ -n "$SDCADM_WRKDIR" ]]; then 37 | WRKDIR=$SDCADM_WRKDIR 38 | TRIM_WRKDIRS=false 39 | if [[ -n "$(echo $WRKDIR | (egrep '^\/var\/sdcadm\/self-updates\/.' || true))" ]]; then 40 | # Be defensive and only allow trimming of `dirname $WRKDIR` if it is 41 | # where we expect it to be. 42 | TRIM_WRKDIRS=true 43 | fi 44 | else 45 | WRKDIR=/var/sdcadm/self-updates/$(date +%Y%m%dT%H%M%SZ) 46 | mkdir -p $WRKDIR 47 | TRIM_WRKDIRS=true 48 | fi 49 | CONFIG_PATH=/var/sdcadm/sdcadm.conf 50 | 51 | 52 | #---- support stuff 53 | 54 | function fatal 55 | { 56 | echo "$0: fatal error: $*" >&2 57 | exit 1 58 | } 59 | 60 | function restore_old_on_error 61 | { 62 | [[ $1 -ne 0 ]] || exit 0 63 | 64 | if [[ -d $OLDDIR ]]; then 65 | echo "$0: restoring $DESTDIR from $OLDDIR" 66 | rm -rf $DESTDIR 67 | mv $OLDDIR $DESTDIR 68 | fi 69 | 70 | fatal "$0: error exit status $1" >&2 71 | } 72 | 73 | 74 | #---- mainline 75 | 76 | # Sanity checks. 77 | [[ "$(zonename)" == "global" ]] || fatal "not running in global zone" 78 | [[ "$(sysinfo | json "Boot Parameters.headnode")" == "true" ]] \ 79 | || fatal "not running on the headnode" 80 | [[ -f "./contents.tgz" ]] || fatal "missing './contents.tgz'" 81 | [[ -d "$WRKDIR" ]] || fatal "'$WRKDIR' does not exist" 82 | 83 | [[ -d $OLDDIR ]] && rm -rf $OLDDIR 84 | [[ -d $NEWDIR ]] && rm -rf $NEWDIR 85 | 86 | trap 'restore_old_on_error $?' EXIT 87 | 88 | mkdir -p $NEWDIR 89 | (cd $NEWDIR && tar xf $TOP/contents.tgz) 90 | rm -rf $NEWDIR/.temp_bin 91 | 92 | # Archive the old sdcadm for possible rollback and log other details. 93 | cp $NEWDIR/package.json $WRKDIR/package.json 94 | cp $NEWDIR/etc/buildstamp $WRKDIR/buildstamp 95 | if [[ -d $DESTDIR ]]; then 96 | echo "Archiving $(cat $DESTDIR/etc/buildstamp) to $WRKDIR/sdcadm.old" 97 | cp -PR $DESTDIR $WRKDIR/sdcadm.old 98 | fi 99 | 100 | if [[ "$TRIM_WRKDIRS" == "true" ]]; then 101 | # Only retain the latest 5 work dirs. 102 | (cd $(dirname $WRKDIR) && ls -1d ????????T??????Z | sort -r | tail +6 \ 103 | | xargs -n1 rm -rf) 104 | fi 105 | 106 | # Move the old out of the way, swap in the new. 107 | if [[ -d $DESTDIR ]]; then 108 | mv $DESTDIR $OLDDIR 109 | fi 110 | mv $NEWDIR $DESTDIR 111 | 112 | # Link-up to get `sdcadm` on the PATH. 113 | rm -f /opt/smartdc/bin/sdcadm 114 | ln -s $DESTDIR/bin/sdcadm /opt/smartdc/bin/sdcadm 115 | 116 | # Link-up to get `sdcadm` on the MANPATH. 117 | rm -f /opt/smartdc/man/man1/sdcadm.1 118 | ln -s $DESTDIR/man/man1/sdcadm.1 /opt/smartdc/man/man1/sdcadm.1 119 | 120 | # Add `serverUuid` to the config (better than having this 121 | # done on every `sdcadm` invocation later). 122 | if [[ ! -f $CONFIG_PATH ]]; then 123 | mkdir -p $(dirname $CONFIG_PATH) 124 | echo '{}' >$CONFIG_PATH 125 | fi 126 | SERVER_UUID=$(sysinfo | json UUID) 127 | json -f $CONFIG_PATH -e "this.serverUuid = '$SERVER_UUID'" >$CONFIG_PATH.new 128 | mv $CONFIG_PATH.new $CONFIG_PATH 129 | 130 | # Import the sdcadm-setup service and gracefully start it. 131 | echo "Importing and starting sdcadm-setup service" 132 | cp $DESTDIR/smf/manifests/sdcadm-setup.xml /var/svc/manifest/site/sdcadm-setup.xml 133 | svccfg import /var/svc/manifest/site/sdcadm-setup.xml 134 | 135 | [[ -d $OLDDIR ]] && rm -rf $OLDDIR 136 | 137 | echo "Successfully upgraded to sdcadm $(cat $DESTDIR/etc/buildstamp)" 138 | exit 0 139 | -------------------------------------------------------------------------------- /tools/md5sum-for-smartos-gz: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # A fake md5sum for gshar to use. Mimicks coreutils' md5sum as much as possible. 14 | # 15 | 16 | num_fails=0 17 | 18 | function checkone() { 19 | expected=$1 20 | path=$2 21 | actual=$(openssl dgst -md5 $path | awk '{print $2}') 22 | if [[ "$actual" == "$expected" ]]; then 23 | echo "$path: OK" 24 | else 25 | echo "$path: FAILED" 26 | num_fails=$(( $num_fails + 1 )) 27 | fi 28 | } 29 | 30 | if [[ $1 == "--version" ]]; then 31 | echo "md5sum from fakey coreutils" 32 | exit 0 33 | fi 34 | if [[ $1 == "-c" ]]; then 35 | shift; 36 | if [[ $# -eq 0 ]]; then 37 | while read line; do 38 | checkone $line 39 | done <&0 40 | else 41 | while [[ $# -ne 0 ]]; do 42 | while read line; do 43 | checkone $line 44 | done <$1 45 | shift 46 | done 47 | fi 48 | if [[ $num_fails -eq 1 ]]; then 49 | echo "md5sum: WARNING: $num_failed computed checksum did NOT match" 50 | elif [[ $num_fails -ne 0 ]]; then 51 | echo "md5sum: WARNING: $num_failed computed checksums did NOT match" 52 | fi 53 | else 54 | openssl dgst -md5 "$@" | sed -E -e 's/^MD5\((.+)\)= (.*)$/\2 \1/' 55 | fi 56 | -------------------------------------------------------------------------------- /tools/mk-shar: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2019, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Make a shar for installing/upgrading sdcadm. 14 | # 15 | 16 | if [[ -n "$TRACE" ]]; then 17 | export PS4='[\D{%FT%TZ}] ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 18 | set -o xtrace 19 | fi 20 | set -o errexit 21 | set -o pipefail 22 | 23 | 24 | TOP=$(cd $(dirname $0)/../ >/dev/null; pwd) 25 | PROTO=$TOP/build/shar-image 26 | 27 | NAME=sdcadm 28 | VERSION=$(json -f $TOP/package.json version) 29 | STAMP=$VERSION-$(date -u "+%Y%m%dT%H%M%SZ") 30 | 31 | 32 | #---- support stuff 33 | 34 | function fatal 35 | { 36 | echo "$0: fatal error: $*" >&2 37 | exit 1 38 | } 39 | 40 | function errexit 41 | { 42 | [[ $1 -ne 0 ]] || exit 0 43 | fatal "error exit status $1" 44 | } 45 | 46 | 47 | 48 | #---- mainline 49 | 50 | # Process arguments. 51 | while getopts "o:s:" c; do 52 | case "$c" in 53 | o) 54 | OUTDIR=$OPTARG 55 | ;; 56 | s) 57 | STAMP=$OPTARG 58 | ;; 59 | *) 60 | usage "illegal option -- $OPTARG" 61 | exit 1 62 | ;; 63 | esac 64 | done 65 | [[ -n "$OUTDIR" ]] || OUTDIR=$TOP/build 66 | mkdir -p $OUTDIR 67 | OUTSHAR=$OUTDIR/$NAME-$STAMP.sh 68 | OUTMANIFEST=$OUTDIR/$NAME-$STAMP.imgmanifest 69 | 70 | shift $((OPTIND - 1)) 71 | 72 | trap 'errexit $?' EXIT 73 | 74 | rm -rf $PROTO 75 | mkdir -p $PROTO 76 | cp -PR \ 77 | $TOP/bin \ 78 | $TOP/lib \ 79 | $TOP/etc \ 80 | $TOP/smf \ 81 | $TOP/test \ 82 | $TOP/man \ 83 | $TOP/node_modules \ 84 | $TOP/tools/install-sdcadm.sh \ 85 | $TOP/package.json \ 86 | $PROTO 87 | cp -PR \ 88 | $TOP/build/node \ 89 | $PROTO/node 90 | mkdir -p $PROTO/tools 91 | cp $TOP/tools/rotate-logs.sh $PROTO/tools/ 92 | 93 | # Trim out cruft for smaller package. 94 | rm -rf $PROTO/node/bin/npm 95 | rm -rf $PROTO/node/lib/node_modules/npm 96 | rm -rf $PROTO/node/share 97 | # TODO: more to trim here: 98 | # find . -name test | xargs du -sk | awk '{print $1}' | paste -sd+ | bc # 1056 99 | 100 | # Add a stamp file to know exactly what build we have. 101 | mkdir -p $PROTO/etc 102 | echo "$STAMP" >$PROTO/etc/buildstamp 103 | 104 | (cd $PROTO \ 105 | && CONTENTS=$(ls | grep -v install-sdcadm.sh | xargs) \ 106 | && gtar -I pigz -cf contents.tgz $CONTENTS \ 107 | && rm -rf $CONTENTS) 108 | 109 | TMP=/var/tmp/$NAME 110 | 111 | (cat <<__EOF__ 112 | #!/bin/bash 113 | # 114 | # This shar will install/upgrade "sdcadm" on a SmartDataCenter headnode GZ. 115 | # 116 | function fatal { 117 | echo "\$0: error: \$*" >&2 118 | exit 3 119 | } 120 | [[ "\$(zonename)" == "global" ]] || fatal "not running in global zone" 121 | [[ "\$(sysinfo | json "Boot Parameters.headnode")" == "true" ]] \ 122 | || fatal "not running on the headnode" 123 | 124 | echo "Extracting sdcadm $STAMP to $TMP." 125 | 126 | if [ \`pwd\` != '$TMP' ]; then 127 | rm -rf $TMP 128 | mkdir -p $TMP 129 | cd $TMP 130 | fi 131 | 132 | # 133 | # The GZ where we will be unsharing does not have md5sum that the shar 134 | # uses to verify contents. 135 | # 136 | mkdir -p $TMP/.temp_bin 137 | export PATH=$TMP/.temp_bin:\$PATH 138 | cat > $TMP/.temp_bin/md5sum << 'EOFTOOL' 139 | __EOF__ 140 | )> $OUTSHAR 141 | 142 | cat ./tools/md5sum-for-smartos-gz >>$OUTSHAR 143 | (cat <<__EOF2__ 144 | EOFTOOL 145 | 146 | chmod +x $TMP/.temp_bin/md5sum 147 | 148 | __EOF2__ 149 | )>>$OUTSHAR 150 | 151 | 152 | (cd $PROTO && shar -Q $(ls) | grep -v '^exit'; cat <> $OUTSHAR 171 | 172 | echo created $OUTSHAR 173 | 174 | cat <$OUTMANIFEST 175 | { 176 | "v": 2, 177 | "uuid": "$(uuid)", 178 | "owner": "00000000-0000-0000-0000-000000000000", 179 | "name": "sdcadm", 180 | "version": "$VERSION", 181 | "description": "Administer SmartDataCenter standups", 182 | "disabled": false, 183 | "public": false, 184 | "type": "other", 185 | "os": "other", 186 | "tags": { 187 | "smartdc_service": "true", 188 | "buildstamp": "$STAMP" 189 | } 190 | } 191 | EOM 192 | echo created $OUTMANIFEST 193 | -------------------------------------------------------------------------------- /tools/rotate-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Consume bunyan log files in appending contents to 14 | # 15 | # Important: files in must be named in the format: 16 | # 17 | # 1358497440806-091018*.log 18 | # 19 | # where the first 13 characters are a number of ms from 00:00 Jan 1, 1970, and 20 | # the set of 6 digits represents the (0-padded) PID of the process that was or is 21 | # writing to the log. 22 | # 23 | # Files for PIDs where the PID does not match an active process will have their 24 | # contents appended to and the file will be deleted. When there is 25 | # an active process, the behavior can be defined through the -c, -i and -m 26 | # options. If more than one of these options is specified, the last one will be 27 | # used. 28 | # 29 | 30 | DEBUG=0 31 | MODE= 32 | VERBOSE=0 33 | 34 | usage() { 35 | if [[ -n $1 ]]; then 36 | echo $* >&2 37 | fi 38 | 39 | cat >&2 < 41 | 42 | Options: 43 | 44 | -c When PID is still running, copy the file to a temp name then truncate old. 45 | -d Enable extra debugging (enables bash's xtrace) 46 | -i When PID is still running, ignore this file. (default) 47 | -m When PID is still running, move the file then consume from the new name. 48 | -v Verbose. Print a message for each file. 49 | 50 | EOF 51 | exit 2 52 | } 53 | 54 | set -- `getopt cdimv $*` 55 | if [ $? != 0 ]; then 56 | usage; 57 | fi 58 | for i in $*; do 59 | case $i in 60 | -c) MODE="copytruncate"; shift;; 61 | -d) DEBUG=1; shift;; 62 | -i) MODE="ignorerunning"; shift;; 63 | -m) MODE="moverunning"; shift;; 64 | -v) VERBOSE=1; shift;; 65 | --) shift; break;; 66 | esac 67 | done 68 | 69 | [[ ${DEBUG} == 1 ]] && set -o xtrace 70 | 71 | dir=$1 72 | targetfile=$2 73 | 74 | [[ -z ${dir} ]] && usage " is required." 75 | [[ -d ${dir} ]] || usage "${dir} is not a directory." 76 | [[ -z ${targetfile} ]] && usage " is required." 77 | 78 | # default to ignoring files for running PIDs. 79 | [[ -z ${MODE} ]] && MODE="ignorerunning" 80 | 81 | for file in $(ls ${dir}); do 82 | 83 | re="^([0-9]{13})-[0]{0,5}([1-9][0-9]{1,5})-.*.log" 84 | if [[ ${file} =~ ${re} ]]; then 85 | timestamp=${BASH_REMATCH[1]} 86 | pid=${BASH_REMATCH[2]} 87 | rotatefile= 88 | 89 | if kill -0 ${pid} 2>/dev/null; then 90 | case ${MODE} in 91 | copytruncate) 92 | # running so we copy to .merging and truncate original, note: 93 | # There's the possibility of losing records here if some come 94 | # in between these calls. 95 | if cp ${dir}/${file} ${dir}/${file}.merging; then 96 | cp /dev/null ${dir}/${file} # truncate 97 | rotatefile=${file}.merging 98 | else 99 | echo "Warning failed to copy ${dir}/${file}" >&2 100 | continue; 101 | fi 102 | [[ ${VERBOSE} == 1 ]] && echo "Rotating (copied) ${file}" 103 | ;; 104 | ignorerunning) 105 | [[ ${VERBOSE} == 1 ]] && echo "Skipping running ${file}" 106 | continue; 107 | ;; 108 | moverunning) 109 | # running so we move to .merging and let VM.js start a new one 110 | if mv ${dir}/${file} ${dir}/${file}.merging; then 111 | rotatefile=${file}.merging 112 | else 113 | echo "Warning failed to copy ${dir}/${file}" >&2 114 | continue; 115 | fi 116 | [[ ${VERBOSE} == 1 ]] && echo "Rotating (moved) ${file}" 117 | ;; 118 | *) 119 | echo "FATAL: internal error, case missing handler for ${MODE}" >&2 120 | exit 1 121 | ;; 122 | esac 123 | else 124 | # not running so don't need to move first. 125 | rotatefile=${file} 126 | [[ ${VERBOSE} == 1 ]] && echo "Rotating ${file}" 127 | fi 128 | 129 | # TODO: bunyan is adding a tool to perform the following for us, use 130 | # that when it's available. 131 | cat ${dir}/${rotatefile} >>${targetfile} \ 132 | && rm -f ${dir}/${rotatefile} 133 | else 134 | echo "Warning: skipping file with incorrect filename: ${file}" >&2 135 | fi 136 | 137 | done 138 | 139 | exit 0 140 | -------------------------------------------------------------------------------- /tools/rsync-to: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2017, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Rsync the master in this working copy to the install on the given HN. 14 | # 15 | 16 | #set -o xtrace 17 | set -o errexit 18 | 19 | TOP=$(cd $(dirname $0)/../; pwd) 20 | NODE=root@$1 21 | [[ -z "$1" ]] && echo 'rsync-to: error: no headnode given' && exit 1 22 | BASEDIR=/opt/smartdc/sdcadm 23 | 24 | extraOpts= 25 | if [[ $(uname -s) != "SunOS" ]]; then 26 | extraOpts="--exclude *.node --exclude build" 27 | else 28 | # Clean node_modules everytime. 29 | ssh $NODE rm -rf $BASEDIR/node_modules 30 | fi 31 | 32 | toSync="bin etc smf lib test package.json" 33 | if [[ -d ${TOP}/node_modules ]]; then 34 | toSync="$toSync node_modules" 35 | fi 36 | for name in $toSync; do 37 | if [[ -d ${TOP}/$name ]]; then 38 | rsync -av ${TOP}/$name/ $NODE:$BASEDIR/$name/ $extraOpts 39 | else 40 | rsync -av ${TOP}/$name $NODE:$BASEDIR/$name $extraOpts 41 | fi 42 | done 43 | rsync -av ${TOP}/tools/rotate-logs.sh $NODE:$BASEDIR/tools/ $extraOpts 44 | 45 | ssh $NODE "$BASEDIR/bin/sdcadm completion > $BASEDIR/etc/sdcadm.completion" --------------------------------------------------------------------------------